/**
 * @file
 *
 * Neighbor discovery and stateless address autoconfiguration for IPv6.
 * Aims to be compliant with RFC 4861 (Neighbor discovery) and RFC 4862
 * (Address autoconfiguration).
 */

/*
 * Copyright (c) 2010 Inico Technologies Ltd.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * This file is part of the lwIP TCP/IP stack.
 *
 * Author: Ivan Delamer <delamer@inicotech.com>
 *
 *
 * Please coordinate changes and requests with Ivan Delamer
 * <delamer@inicotech.com>
 */

#include "lwip/opt.h"

#if LWIP_IPV6  /* don't build if not configured for use in lwipopts.h */
#include "lwip/sys.h"
#include "lwip/nd6.h"
#include "lwip/priv/nd6_priv.h"
#include "lwip/prot/nd6.h"
#include "lwip/prot/icmp6.h"
#include "lwip/pbuf.h"
#include "lwip/mem.h"
#include "lwip/memp.h"
#include "lwip/ip6.h"
#include "lwip/ip6_addr.h"
#include "lwip/inet_chksum.h"
#include "lwip/netif.h"
#include "lwip/icmp6.h"
#include "lwip/mld6.h"
#include "lwip/dhcp6.h"
#include "lwip/ip.h"
#include "lwip/stats.h"
#include "lwip/dns.h"

#include <string.h>

#ifdef LWIP_HOOK_FILENAME
#include LWIP_HOOK_FILENAME
#endif

/*This macro will be used in RA-prefix processing for deciding whether we
should perform updation operation on valid lifetime, RA has information in sec
so TWO_HOURS is in seconds*/

#ifndef LWIP_TWO_HOURS
#define LWIP_TWO_HOURS 7200
#endif

#if LWIP_IPV6_DUP_DETECT_ATTEMPTS > IP6_ADDR_TENTATIVE_COUNT_MASK
#error LWIP_IPV6_DUP_DETECT_ATTEMPTS > IP6_ADDR_TENTATIVE_COUNT_MASK
#endif
u32_t is_dup_detect_initialized = 0;
sys_sem_t dup_addr_detect;
/* Router tables. */
struct nd6_neighbor_cache_entry neighbor_cache[LWIP_ND6_NUM_NEIGHBORS];
struct nd6_destination_cache_entry destination_cache[LWIP_ND6_NUM_DESTINATIONS];
struct nd6_router_list_entry default_router_list[LWIP_ND6_NUM_ROUTERS];

/* Default values, can be updated by a RA message. */
u32_t reachable_time = LWIP_ND6_REACHABLE_TIME;
u32_t retrans_timer = LWIP_ND6_RETRANS_TIMER; /* @todo implement this value in timer */

/* Index for cache entries. */
static u8_t nd6_cached_neighbor_index;
static u8_t nd6_cached_destination_index;

/* Multicast address holder. */
static ip6_addr_t multicast_address;

/* rs timer precision */
static u8_t nd6_tmr_rs_reduction;

/* Static buffer to parse RA packet options (size of a prefix option, biggest option) */
static u8_t nd6_ra_buffer[sizeof(struct prefix_option)];

/* Static buffer to parse RD packet options (size of a minium lladdr as biggest option) */
static u8_t rd6_ra_buffer[sizeof(struct lladdr_option)];

/* Forward declarations. */
static s8_t nd6_find_neighbor_cache_entry(const ip6_addr_t *ip6addr, struct netif *netif);
static s8_t nd6_new_neighbor_cache_entry(void);
static void nd6_free_neighbor_cache_entry(s8_t i);
static s8_t nd6_find_destination_cache_entry(const ip6_addr_t *ip6addr);
static s8_t nd6_new_destination_cache_entry(void);
static s8_t nd6_is_prefix_in_netif(const ip6_addr_t *ip6addr, struct netif *netif);
static s8_t nd6_select_router(const ip6_addr_t *ip6addr, struct netif *netif);
static s8_t nd6_get_router(const ip6_addr_t *router_addr, struct netif *netif);
static s8_t nd6_new_router(const ip6_addr_t *router_addr, struct netif *netif);
static s8_t nd6_get_onlink_prefix(ip6_addr_t *prefix, struct netif *netif);
static s8_t nd6_new_onlink_prefix(ip6_addr_t *prefix, struct netif *netif);
static s8_t nd6_get_next_hop_entry(const ip6_addr_t *ip6addr, struct netif *netif);
static err_t nd6_queue_packet(s8_t neighbor_index, struct pbuf *q);
static void  nd6_free_router(s8_t i);
static int   nd6_neighbor_update(struct netif *inp, struct lladdr_option *lladdr_opt, s8_t i, u32_t flags);

static void nd6_send_ns(struct netif *netif, const ip6_addr_t *target_addr, u8_t flags);
void nd6_send_na(struct netif *netif, const ip6_addr_t *target_addr, u8_t flags);
static void nd6_send_neighbor_cache_probe(struct nd6_neighbor_cache_entry *entry, u8_t flags);
#if LWIP_IPV6_SEND_ROUTER_SOLICIT
err_t nd6_send_rs(struct netif *netif);
#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */

#if LWIP_ND6_QUEUEING
static void nd6_free_q(struct nd6_q_entry *q);
#else /* LWIP_ND6_QUEUEING */
#define nd6_free_q(q) (void)pbuf_free(q)
#endif /* LWIP_ND6_QUEUEING */
static void nd6_send_q(s8_t i);
#ifdef LWIP_TESTBED
void nd6_remove_netif_neighbor_cache_entries(struct netif *netif) {
  int i;

  /* iterate through neighbour cache */
  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++){
    if (neighbor_cache[i].netif == netif){
      neighbor_cache[i].isrouter = 0;
      nd6_free_neighbor_cache_entry(i);
    }
  }
}
#endif

#if defined(LWIP_RPL) && LWIP_RPL
void *
nd6_add_default_router(const ip6_addr_t * router_addr,
      u32_t lifetime,//in seconds mostly
      struct netif * inp){
  /* query if entry already exists */
  s16_t idx;

  /* Get the matching default router entry. */
  idx = nd6_get_router(router_addr, inp);
  if (idx < 0) {
    /* Create a new router entry. */
    idx = nd6_new_router(router_addr, inp);
  }

  if (idx < 0) {
    /* Could not create a new router entry. */
    ND6_STATS_INC(nd6.cacheFull);
    return NULL;
  }

  /* Re-set invalidation timer. */
  default_router_list[idx].invalidation_timer = lifetime;
  default_router_list[idx].flags = 0;
  return (void*)&default_router_list[idx];
}

void
nd6_remove_default_router(void *default_route){

  struct nd6_router_list_entry *entry;

  if (NULL == default_route){
    return;
  }

  entry = (struct nd6_router_list_entry *)default_route;

  if (NULL != entry->neighbor_entry){
     entry->neighbor_entry->isrouter = 0;
    return;
  }

  entry->neighbor_entry = NULL;
  entry->invalidation_timer = 0;
  entry->flags = 0;
  return;
}



err_t
nd6_get_default_router_ip(ip6_addr_t * router_addr, void *default_route){

  struct nd6_router_list_entry *entry;

  if (NULL == default_route){
    return ERR_VAL;
  }

  entry = (struct nd6_router_list_entry *)default_route;

  if (NULL == entry->neighbor_entry){
    return ERR_VAL;
  }

  ip6_addr_copy_ptr(router_addr, &entry->neighbor_entry->next_hop_address);

  return ERR_OK;
}

#endif

/* [DTS2018052510354]- [TEST][lwIP][FUNC]: Codenomicon for ICMPv6 fails due to redirect.*/
struct nd6_opt_hdr {
  u8_t    nd_opt_type;
  u8_t    nd_opt_len;
}nd6_opt_hdr_t;


int nd6_validate_options(u8_t *opt, int opt_len)
{
  struct nd6_opt_hdr *nd_opt = (struct nd6_opt_hdr *)opt;
  int cnt = 0;
  if ((nd_opt == NULL) || opt_len < 0)
    return -1;
  while (opt_len) {
    int l;
    if ((u32_t)opt_len < sizeof(struct nd6_opt_hdr))
      return -1;
    l = nd_opt->nd_opt_len << 3;
    if (opt_len < l || l == 0)
      return -1;
    opt_len -= l;
    nd_opt = (struct nd6_opt_hdr *)(((char *)nd_opt) + l);
    cnt++;
  }
  return cnt;
}

/* Update neighbor cache table.
 * -- lladdr is new lladdr or NULL, if it is not supplied.
 * -- flags
 * OVERRIDE allows to override existing lladdr, if it is different.
 * ISROUTER indicates if the neighbour is known as a router.
 * Caller MUST hold reference count on the entry.
 *
 * @param lladdr_opt the link layer address option
 * @return 1 if success, -1 otherwise
 */
static int
nd6_neighbor_update(struct netif *inp, struct lladdr_option *lladdr_opt, s8_t i, u32_t flags)
{
  s32_t is_llchange = 0;/* Link layer address changes*/
  u8_t is_solicited;    /* Solicited ND message*/
  u8_t is_override;     /* Override LLADDR message*/
  u8_t is_router;       /* Is Router Message*/
  s8_t idx;             /* Router Index*/

  /* Get solicited flag*/
  is_solicited = (flags & ND6_FLAG_SOLICITED);
  /* Get override flag*/
  is_override  = (flags& ND6_FLAG_OVERRIDE);

  /* If the target's Neighbor Cache entry is in the INCOMPLETE state when the advertisement is received,
     one of two things happens.  If the link layer has addresses and no Target Link-Layer Address option is
     included, the receiving node SHOULD silently discard the received advertisement.  Otherwise,
     the receiving node performs the following steps:
  */
  if (neighbor_cache[i].state == ND6_INCOMPLETE) {

    if (lladdr_opt == NULL) {
      /*Not a valid message*/
      LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_WARNING, ("ICMPv6 NA: Invalid Option.\n"));
      ND6_STATS_INC(nd6.proterr);
      return -1;
    }

    /* - It records the link-layer address in the Neighbor Cache entry.*/
    (void)memcpy_s(neighbor_cache[i].lladdr, NETIF_MAX_HWADDR_LEN, lladdr_opt->addr, inp->hwaddr_len);

    /* - If the advertisement's Solicited flag is set, the state of the entry is set to REACHABLE;
     *   otherwise, it is set to STALE.
     */
    if (is_solicited) {
      neighbor_cache[i].state = ND6_REACHABLE;
      neighbor_cache[i].counter.reachable_time = reachable_time;
    } else {
      neighbor_cache[i].state = ND6_STALE;
    }
    /*save the interface*/
    neighbor_cache[i].netif = inp;
  } else {
    /*Check is there LLADDR change,*/
    if (lladdr_opt != NULL){
      is_llchange = !lwip_lladdr_cmp(neighbor_cache[i].lladdr, lladdr_opt->addr);
    }
    /* If the Override flag is clear and the supplied link-layer address differs from that in the cache,
     * then one of two actions takes place:
     *    a. If the state of the entry is REACHABLE, set it to STALE, but do not update the entry in any other way.
     */
    if (!is_override && is_llchange){
       if (neighbor_cache[i].state == ND6_REACHABLE) {
         neighbor_cache[i].state = ND6_STALE;
       }
       /* b. Otherwise, the received advertisement should be ignored and MUST NOT update the cache.*/
       LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_WARNING, ("ICMPv6 NA: Override flag is clear and LLADDR differs from the cache.\n"));
       return -1;

    } else {
      /*  - The link-layer address in the Target Link-Layer Address option MUST be inserted in the cache
       *    (if one is supplied and differs from the already recorded address)
       */
      if (is_override || ((lladdr_opt != NULL) && !is_llchange) || (lladdr_opt == NULL)) {
        /*If target LLADDR option present, insert to cache*/
        if (lladdr_opt != NULL){
          (void)memcpy_s(neighbor_cache[i].lladdr, NETIF_MAX_HWADDR_LEN, lladdr_opt->addr, inp->hwaddr_len);
        }
        /* - If the advertisement's Solicited flag is set, the state of the entry is set to REACHABLE;
         *   otherwise, it is set to STALE.
         */
        if (is_solicited) {
          neighbor_cache[i].state = ND6_REACHABLE;
          neighbor_cache[i].counter.reachable_time = reachable_time;
        } else if (is_llchange) {
          neighbor_cache[i].state = ND6_STALE;
        }
      }
    }
  }

  /* Get router flag*/
  is_router    = (flags & ND6_FLAG_ROUTER) ? 1 : 0;

  /* - It sets the IsRouter flag in the cache entry based on the Router flag in the received advertisement.*/

  /* In those cases where the IsRouter flag changes from TRUE to FALSE as a result of this update,
   * the node MUST remove that router from the Default Router List and update the Destination Cache entries
   * for all destinations using that neighbor as a router as specified in Section 7.3.3.
   */
  if(neighbor_cache[i].isrouter && !is_router) {
    idx = nd6_get_router(ip6_current_src_addr(), inp);
    /*Remove from Default Router List*/
    nd6_free_router(idx);
  }
  neighbor_cache[i].isrouter = is_router;

  /* Send queued packets, if any. */
  if (neighbor_cache[i].q != NULL) {
    nd6_send_q(i);
  }

  return 1;
}

/*
 * A local address has been determined to be a duplicate. Take the appropriate
 * action(s) on the address and the interface as a whole.
 *
 * @param netif the netif that owns the address
 * @param addr_idx the index of the address detected to be a duplicate
 */
static void
nd6_duplicate_addr_detected(struct netif *netif, s8_t addr_idx)
{

  /* Mark the address as duplicate, but leave its lifetimes alone. If this was
   * a manually assigned address, it will remain in existence as duplicate, and
   * as such be unusable for any practical purposes until manual intervention.
   * If this was an autogenerated address, the address will follow normal
   * expiration rules, and thus disappear once its valid lifetime expires. */
  netif_ip6_addr_set_state(netif, addr_idx, IP6_ADDR_DUPLICATED);

#if LWIP_IPV6_AUTOCONFIG
  /* If the affected address was the link-local address that we use to generate
   * all other addresses, then we should not continue to use those derived
   * addresses either, so mark them as duplicate as well. For autoconfig-only
   * setups, this will make the interface effectively unusable, approaching the
   * intention of RFC 4862 Sec. 5.4.5. @todo implement the full requirements */
  if (addr_idx == 0) {
    s8_t i;
    for (i = 1; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
      if (!ip6_addr_isinvalid(netif_ip6_addr_state(netif, i))) {
        netif_ip6_addr_set_state(netif, i, IP6_ADDR_DUPLICATED);
      }
    }
  }
#endif /* LWIP_IPV6_AUTOCONFIG */
}

/*
 * Process an incoming neighbor discovery message
 *
 * @param p the nd packet, p->payload pointing to the icmpv6 header
 * @param inp the netif on which this packet was received
 */
void
nd6_input(struct pbuf *p, struct netif *inp)
{
  u8_t msg_type;
  s8_t i;

  ND6_STATS_INC(nd6.recv);

  msg_type = *((u8_t *)p->payload);
  switch (msg_type) {
  case ICMP6_TYPE_NA: /* Neighbor Advertisement. */
  {
    struct na_header *na_hdr;
    struct lladdr_option *lladdr_opt;
    ip6_addr_t target_address;
    int ret;

    /* Check that NA header fits in packet. */
    if (p->len < (sizeof(struct na_header))) {
      /* @todo debug message */
      (void)pbuf_free(p);
      ND6_STATS_INC(nd6.lenerr);
      ND6_STATS_INC(nd6.drop);
      return;
    }

    /* Check that NA header HL is 255. */
    if (ND6_HOPLIM != IP6H_HOPLIM(ip6_current_header())) {
      LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ICMPv6 NA header Hop Limit is not 255, packet dropped.\n"));
      (void)pbuf_free(p);
      ND6_STATS_INC(nd6.drop);
      return;
    }

    na_hdr = (struct na_header *)p->payload;

    /* Check that NA header code is 0. */
    if (0 != na_hdr->code) {
      LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
         ("ICMPv6 NA header (code %"X8_F") is not zero, packet dropped.\n", na_hdr->code));
      (void)pbuf_free(p);
      ND6_STATS_INC(nd6.drop);
      return;
    }

    /**
    @page RFC-4861 RFC-4861
    @par  RFC-4861 Compliance Information
    @par Compliant Section
    Section 7.1.2. Validation of Neighbor Advertisements
    @par Behavior Description
    Test v6LC.2.1.15: Invalid Neighbor Advertisement Handling-Part F: NUT receives invalid NA (target == multicast address)\n
    Behavior:  Silently discard any received Neighbor Advertisement> \n
	 If the IP Destination Address is a multicast address the Solicited flag is zero)\n

    */

    /* Create an aligned copy. */
    ip6_addr_set(&target_address, &(na_hdr->target_address));

    if (ip6_addr_ismulticast(&target_address)) {
        LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_WARNING, ("ICMPv6 NA: Discarding NA :target address is multicast.\n"));
        (void)pbuf_free(p);
        ND6_STATS_INC(nd6.drop);
        return;
    }

    /* If the target's Neighbor Cache entry is in the INCOMPLETE state when
     * the advertisement is received, one of two things happens.  If the
     * link layer has addresses and no Target Link-Layer Address option is
     * included, the receiving node SHOULD silently discard the received
     * advertisement.  Otherwise, the receiving node performs the following
     * steps:

     - It records the link-layer address in the Neighbor Cache entry.

     - If the advertisement's Solicited flag is set, the state of the
       entry is set to REACHABLE; otherwise, it is set to STALE.

     - It sets the IsRouter flag in the cache entry based on the Router
       flag in the received advertisement.

     - It sends any packets queued for the neighbor awaiting address
       resolution.
    */

    /* Unsolicited NA?*/
    if (ip6_addr_ismulticast(ip6_current_dest_addr())) {
      /* This is an unsolicited NA.
       * link-layer changed?
       * part of DAD mechanism? */
      /**
      @page RFC-4861

      @par Compliant Section
      Section 7.1.2. Validation of Neighbor Advertisements
      @par Behavior Description
      If the IP Destination Address is a multicast address the Solicited flag is zero)\n
      Behavior:  Silently discard any received Neighbor Advertisement
      */
      if (na_hdr->flags & ND6_FLAG_SOLICITED) {
        LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_WARNING, ("ICMPv6 NA: solicited NA is multicasted.\n"));
        (void)pbuf_free(p);
        ND6_STATS_INC(nd6.drop);
        return;
      }
#if LWIP_IPV6_DUP_DETECT_ATTEMPTS
      /* If the target address matches this netif, it is a DAD response. */
      for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
        if (!ip6_addr_isinvalid(netif_ip6_addr_state(inp, i)) &&
            !ip6_addr_isduplicated(netif_ip6_addr_state(inp, i)) &&
            ip6_addr_cmp(&target_address, netif_ip6_addr(inp, i))) {
          /*[DTS2018050703246][Ignore the NA tgt address same as stack address ]*/
          if (ip6_addr_isvalid(netif_ip6_addr_state(inp, i))){
            /* just ignore the packet and do not process it further */
            (void)pbuf_free(p);
            ND6_STATS_INC(nd6.drop);
            return;
          }

          /* We are using a duplicate address. */
          nd6_duplicate_addr_detected(inp, i);

          if (inp->ipv6_addr_event_cb != NULL) {
            inp->ipv6_addr_event_cb(inp, *netif_ip6_addr(inp, i), netif_ip6_addr_state(inp, i));
          }

          if (is_dup_detect_initialized){
              sys_sem_signal(&dup_addr_detect);
          }

          /* DTS2018021001733- Once DAD on link local fails, it is not supposed to send/recv anything */
          if (ip6_addr_islinklocal(&target_address)) {
            (void)netif_set_down(inp);
          }
          else {
#if LWIP_IPV6_DHCP6 & LWIP_IPV6_DHCP6_STATEFUL
            dhcp6_dad_handle(inp, &target_address);
#endif
#if LWIP_IPV6_AUTOCONFIG
            /* Check to see if this address was autoconfigured. */
            if (!ip6_addr_islinklocal(&target_address)) {
              i = nd6_get_onlink_prefix(&target_address, inp);
              if (i >= 0) {
                /* Mark this prefix as duplicate, so that we don't use it
                 * to generate this address again. */
                inp->prefix_list[i].flags |= ND6_PREFIX_AUTOCONFIG_ADDRESS_DUPLICATE;

              }
            }
#endif /* LWIP_IPV6_AUTOCONFIG */
          }
          (void)pbuf_free(p);
          return;
        }
      }
#endif /* LWIP_IPV6_DUP_DETECT_ATTEMPTS */

      /* Check that link-layer address option also fits in packet. */
      if (p->len < (sizeof(struct na_header) + 2)) {
        /* @todo debug message */
        (void)pbuf_free(p);
        ND6_STATS_INC(nd6.lenerr);
        ND6_STATS_INC(nd6.drop);
        return;
      }

      lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct na_header));

      if ((0 == lladdr_opt->length) || (lladdr_opt->type != ND6_OPTION_TYPE_TARGET_LLADDR) ||
          (p->len < (u16_t)((sizeof(struct na_header) + (unsigned int)(lladdr_opt->length << 3))))) {
        LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_WARNING, ("ICMPv6 NA: Invalid Option.\n"));
        (void)pbuf_free(p);
        ND6_STATS_INC(nd6.lenerr);
        ND6_STATS_INC(nd6.drop);
        return;
      }

      /* This is an unsolicited NA, most likely there was a LLADDR change. */
      i = nd6_find_neighbor_cache_entry(&target_address, inp);

      /* Validates return value immediately else codedex warns about it */
      if (i < 0) {
        /* Entry is not present in the Cache,drop it. */
        ND6_STATS_INC(nd6.cacheMiss);
        (void)pbuf_free(p);
        return;
      }

    } else {

      /* This is a solicited NA.
       * neighbor address resolution response?
       * neighbor unreachability detection response? */

      /* Find the cache entry corresponding to this na. */
      i = nd6_find_neighbor_cache_entry(&target_address, inp);
      if (i < 0) {
        /* We no longer care about this target address. drop it. */
        ND6_STATS_INC(nd6.cacheMiss);
        (void)pbuf_free(p);
        return;
      }

      /* Check if there is a link-layer address provided. Only point to it if in this buffer. */
      if (p->len >= (sizeof(struct na_header) + 2)) {
        lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct na_header));

        if ((!lladdr_opt->length) || (lladdr_opt->type != ND6_OPTION_TYPE_TARGET_LLADDR) ||
           (p->len < (u16_t)((sizeof(struct na_header) + (unsigned int)(lladdr_opt->length << 3))))) {
           lladdr_opt = NULL;
        }
        /*Yes Option is valid*/
      } else {
        /*No LLADDR option or Invalid*/
        lladdr_opt = NULL;
      }
    }

    /*Update Neighbor cache entries*/
    ret = nd6_neighbor_update(inp, lladdr_opt, i, na_hdr->flags);
    if (ret < 0){
      LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ICMPv6 neighbor update failed ret(%"S32_F").\n", ret));
      ND6_STATS_INC(nd6.drop);
      (void)pbuf_free(p);
      return;
    }

    break; /* ICMP6_TYPE_NA */
  }
  case ICMP6_TYPE_NS: /* Neighbor solicitation. */
  {
    struct ns_header *ns_hdr;
    struct lladdr_option *lladdr_opt;
    u8_t accepted;

    /* Check that NS header fits in packet. */
    if (p->len < sizeof(struct ns_header)) {
      /* @todo debug message */
      (void)pbuf_free(p);
      ND6_STATS_INC(nd6.lenerr);
      ND6_STATS_INC(nd6.drop);
      return;
    }

    /* Check that NS header HL is 255. */
    if (ND6_HOPLIM != IP6H_HOPLIM(ip6_current_header())) {
        LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("ICMPv6 NS header Hop Limit is not 255, packet dropped.\n"));
        (void)pbuf_free(p);
        ND6_STATS_INC(nd6.drop);
        return;
    }

    ns_hdr = (struct ns_header *)p->payload;

    if (0 != ns_hdr->code) {
        LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
          ("ICMPv6 NS header (code %"X8_F") is not zero, packet dropped.\n", ns_hdr->code));
        (void)pbuf_free(p);
        ND6_STATS_INC(nd6.drop);
        return;
    }

    /* Check if there is a link-layer address provided. Only point to it if in this buffer. */
    if (p->len >= (sizeof(struct ns_header) + 2)) {
      lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct ns_header));
      if ((lladdr_opt == NULL ) || (!lladdr_opt->length) || (lladdr_opt->type != ND6_OPTION_TYPE_SOURCE_LLADDR) ||
        (p->len < (u16_t)((sizeof(struct ns_header) + (unsigned int)(lladdr_opt->length << 3))))) {
        lladdr_opt = NULL;
      }
      else
      {
        /**
        @page RFC-4861 RFC-4861
        @par Compliant Section
        Section 7.1.1  Validation of Neighbor Solicitations
        @par Behavior Description
        If the IP source address is the unspecified address, there is no source link-layer address option in the message.\n
         (If src address is unpecified then address option should not be of type source ll address\n
        Behavior:  Silently discard any received Neighbor Solicitations
        */
        if ((ip6_addr_isany_val(*(ip6_current_src_addr()))) && (lladdr_opt->type == ND6_OPTION_TYPE_SOURCE_LLADDR))
        {
          LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
            ("ICMPv6 NS header option type %"X8_F" , and src address is :: so packet dropped.\n", ND6_OPTION_TYPE_SOURCE_LLADDR));
          (void)pbuf_free(p);
          ND6_STATS_INC(nd6.drop);
          return;
        }
      }
    } else {
      lladdr_opt = NULL;
    }

    /* Check if the target address is configured on the receiving netif. */
    accepted = 0;
    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; ++i) {
      if ((ip6_addr_isvalid(netif_ip6_addr_state(inp, i)) ||
           (ip6_addr_istentative(netif_ip6_addr_state(inp, i)) &&
            ip6_addr_isany_val((*ip6_current_src_addr())))) &&
          ip6_addr_cmp(&(ns_hdr->target_address), netif_ip6_addr(inp, i))) {
        accepted = 1;
        break;
      }
    }

    /* NS not for us? */
    if (!accepted) {
      (void)pbuf_free(p);
      return;
    }

    /* Check for ANY address in src (DAD algorithm). */
    if (ip6_addr_isany_val((*ip6_current_src_addr()))) {
      ip6_addr_t target_address;
            /* Create an aligned copy. */
      ip6_addr_set(&target_address, &(ns_hdr->target_address));

      /* Sender is validating this address. */
      for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; ++i) {
        if (!ip6_addr_isinvalid(netif_ip6_addr_state(inp, i)) &&
          ip6_addr_cmp(&target_address, netif_ip6_addr(inp, i))) {
          if (ip6_addr_istentative(netif_ip6_addr_state(inp, i))) {
            /* We shouldn't use this address either. */
            nd6_duplicate_addr_detected(inp, i);
            /* do not use any of address on this interface */
            /* DTS2018021001733- Once DAD on link local fails, it is not supposed to send/recv anything */
            if (ip6_addr_islinklocal(netif_ip6_addr(inp, i))) {
              (void)netif_set_down(inp);
              (void)pbuf_free(p);
              return;
            }
          }
          else {
            /* Send a NA back so that the sender does not use this address. */
            nd6_send_na(inp, netif_ip6_addr(inp, i), ND6_FLAG_OVERRIDE | ND6_SEND_FLAG_ALLNODES_DEST);
          }
        }
      }
    } else {
      ip6_addr_t target_address;

      /* Sender is trying to resolve our address. */
      /* Verify that they included their own link-layer address. */
      if (lladdr_opt == NULL) {
        /* Not a valid message. */
        (void)pbuf_free(p);
        ND6_STATS_INC(nd6.proterr);
        ND6_STATS_INC(nd6.drop);
        return;
      }


      /* [DTS2018052510354]- [TEST][lwIP][FUNC]: Codenomicon for ICMPv6 fails due to redirect.*/
      /* Validate incoming packet - Do not add neighbor cache entry for self*/
      for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; ++i) {
        if (!ip6_addr_isinvalid(netif_ip6_addr_state(inp, i)) &&
            ip6_addr_cmp(ip6_current_src_addr(), netif_ip6_addr(inp, i))) {
            /* Not a valid message. */
            (void)pbuf_free(p);
            ND6_STATS_INC(nd6.proterr);
            ND6_STATS_INC(nd6.drop);
            return;
        }
      }

      i = nd6_find_neighbor_cache_entry(ip6_current_src_addr(), inp);
      if (i>= 0) {
        /* We already have a record for the solicitor. */
        if (neighbor_cache[i].state == ND6_INCOMPLETE) {
          neighbor_cache[i].netif = inp;
          (void)memcpy_s(neighbor_cache[i].lladdr, NETIF_MAX_HWADDR_LEN, lladdr_opt->addr, inp->hwaddr_len);

          /* Delay probe in case we get confirmation of reachability from upper layer (TCP). */
          neighbor_cache[i].state = ND6_DELAY;
          neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME / ND6_TMR_INTERVAL;
        }
      } else {
        /* Add their IPv6 address and link-layer address to neighbor cache.
         * We will need it at least to send a unicast NA message, but most
         * likely we will also be communicating with this node soon. */
        i = nd6_new_neighbor_cache_entry();
        if (i < 0) {
          /* We couldn't assign a cache entry for this neighbor.
           * we won't be able to reply. drop it. */
          (void)pbuf_free(p);
          ND6_STATS_INC(nd6.cacheFull);
          return;
        }
        neighbor_cache[i].netif = inp;
        (void)memcpy_s(neighbor_cache[i].lladdr, NETIF_MAX_HWADDR_LEN, lladdr_opt->addr, inp->hwaddr_len);
        ip6_addr_set_val(&(neighbor_cache[i].next_hop_address), ip6_current_src_addr());

        /* Receiving a message does not prove reachability: only in one direction.
         * Delay probe in case we get confirmation of reachability from upper layer (TCP). */
        neighbor_cache[i].state = ND6_DELAY;
        neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME / ND6_TMR_INTERVAL;
      }

      /* Create an aligned copy. */
      ip6_addr_set(&target_address, &(ns_hdr->target_address));

      /* Send back a NA for us. Allocate the reply pbuf. */
      nd6_send_na(inp, &target_address, ND6_FLAG_SOLICITED | ND6_FLAG_OVERRIDE);
    }

    break; /* ICMP6_TYPE_NS */
  }
  case ICMP6_TYPE_RA: /* Router Advertisement. */
  {
    struct ra_header *ra_hdr = NULL;
    u8_t *buffer = NULL; /* Used to copy options. */
    u16_t offset;
#if LWIP_ND6_RDNSS_MAX_DNS_SERVERS
    /* There can by multiple RDNSS options per RA */
    u8_t rdnss_server_idx = 0;
#endif /* LWIP_ND6_RDNSS_MAX_DNS_SERVERS */
    /*DTS2018071212996:RA should be discarded if the ipv6 src is not a link-local*/

    /**
    @page RFC-4861 RFC-4861
    @par Compliant Section
    Section 4.2 : Router Advertisement Message Format
    @par Behavior Description
    Source Address MUST be the link-local address assigned to the interface from which this message is sent
    Behavior:  Silently discard any received Router Advertisement
    @par Non-Compliant Section
    Cur Hop Limit  handling is not supported.
    8-bit unsigned integer. The default value that should be placed in the Hop Count field of the IP header
     for outgoing IP packets. A value of zero means unspecified (by this router).
    */
    if (!ip6_addr_islinklocal(ip6_current_src_addr())) {
      LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_WARNING, ("ICMPv6 RA: source address is not link-local.\n"));
      (void)pbuf_free(p);
      ND6_STATS_INC(nd6.proterr);
      ND6_STATS_INC(nd6.drop);
      return;
    }
    /* Check that RA header fits in packet. */
    if (p->len < sizeof(struct ra_header)) {
      /* @todo debug message */
      (void)pbuf_free(p);
      ND6_STATS_INC(nd6.lenerr);
      ND6_STATS_INC(nd6.drop);
      return;
    }

    /* Check that RA header HL is 255. */
    if (ND6_HOPLIM != IP6H_HOPLIM(ip6_current_header())) {
      LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_WARNING, ("ICMPv6 RA: header Hop Limit is not 255, packet dropped.\n"));
      (void)pbuf_free(p);
      ND6_STATS_INC(nd6.drop);
      return;
    }

    ra_hdr = (struct ra_header *)p->payload;

    if (0 != ra_hdr->code) {
      LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_WARNING, ("ICMPv6 RA: header (code %"X8_F") is not zero, packet dropped.\n", ra_hdr->code));
      (void)pbuf_free(p);
      ND6_STATS_INC(nd6.drop);
      return;
    }

    /* If we are sending RS messages, stop. */
#if LWIP_IPV6_SEND_ROUTER_SOLICIT
    /* ensure at least one solicitation is sent */
    if ((inp->rs_count < LWIP_ND6_MAX_MULTICAST_SOLICIT) ||
        (nd6_send_rs(inp) == ERR_OK)) {
      inp->rs_count = 0;
    }
#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */

    /* Get the matching default router entry. */
    i = nd6_get_router(ip6_current_src_addr(), inp);
    if ((i < 0) && (0 != lwip_htons(ra_hdr->router_lifetime))) {
      /* Create a new router entry. */
      i = nd6_new_router(ip6_current_src_addr(), inp);
    }

    if (i < 0) {
      /* Could not create a new router entry. */
      (void)pbuf_free(p);
      ND6_STATS_INC(nd6.cacheFull);
      return;
    }

    /* Re-set invalidation timer. */
    default_router_list[i].invalidation_timer = lwip_htons(ra_hdr->router_lifetime);

    /*RA update:Remove the entry from default router list immediately rather than waiting for timer expiry*/
    if (0 == ra_hdr->router_lifetime){
      default_router_list[i].neighbor_entry = NULL;
    }

    /* Re-set default timer values. */
#if LWIP_ND6_ALLOW_RA_UPDATES
    if (ra_hdr->retrans_timer > 0) {
      retrans_timer = lwip_htonl(ra_hdr->retrans_timer);
    }
    if (ra_hdr->reachable_time > 0) {
      reachable_time = lwip_htonl(ra_hdr->reachable_time);
    }
#endif /* LWIP_ND6_ALLOW_RA_UPDATES */

    /* @todo set default hop limit... */
    /* ra_hdr->current_hop_limit;*/

    /* Update flags in local entry (incl. preference). */
    default_router_list[i].flags = ra_hdr->flags;

#if LWIP_IPV6_DHCP6
    /* Trigger DHCPv6 if enabled */
    dhcp6_nd6_ra_trigger(inp, ra_hdr->flags & ND6_RA_FLAG_MANAGED_ADDR_CONFIG,
      ra_hdr->flags & ND6_RA_FLAG_OTHER_CONFIG);
#endif

    /* Offset to options. */
    offset = sizeof(struct ra_header);

    /*Validate options*/
    while ((p->tot_len - offset) >= 2) {
      u16_t option_len;
      int  opt_type;
      int option_len8 = pbuf_try_get_at(p, (u16_t)(offset + 1));
      if (option_len8 <= 0) {
        LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
              ("IPv6 options header (optlen %d) is not specified, packet dropped.\n",
              option_len8));
            /* read beyond end or zero length */
        goto lenerr_drop_free_return;
      }

      option_len = (u16_t)(((u8_t)option_len8) << 3);
      if (option_len > p->tot_len - offset) {
        LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
          ("IPv6 options header (optlen %d) does not fit in (total len %"U16_F") and (offset %"U16_F"), packet dropped.\n",
              option_len8, p->tot_len, offset));
        /* short packet (option does not fit in) */
        goto lenerr_drop_free_return;
      }

      opt_type =  pbuf_try_get_at(p, offset);
      LWIP_ASSERT("opt_type >= 0", opt_type >= 0);

      switch (opt_type) {
      case ND6_OPTION_TYPE_SOURCE_LLADDR:
      {

        if (sizeof(struct lladdr_option) != option_len) {
          LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
          ("ND6_OPTION_TYPE_SOURCE_LLADDR: (optlen %"U16_F") is less than struct lladdr_option or not same as HWADDR length, packet dropped.\n",
              option_len));
          goto lenerr_drop_free_return;
        }
        break;
      }
      case ND6_OPTION_TYPE_MTU:
      {
        if (option_len < sizeof(struct mtu_option)) {
          LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
          ("ND6_OPTION_TYPE_MTU: (optlen %"U16_F") is less than struct mtu_option, packet dropped.\n",
              option_len));
          goto lenerr_drop_free_return;
        }
        break;
      }
      case ND6_OPTION_TYPE_PREFIX_INFO:
      {
        if (option_len < sizeof(struct prefix_option)) {
          LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
          ("ND6_OPTION_TYPE_PREFIX_INFO: (optlen %"U16_F") is less than struct prefix_option, packet dropped.\n",
              option_len));
          goto lenerr_drop_free_return;
        }

        break;
      }

#if LWIP_ND6_RDNSS_MAX_DNS_SERVERS
      case ND6_OPTION_TYPE_RDNSS:
      {
        u8_t num, n;
        struct rdnss_option * rdnss_opt;

        if (option_len < SIZEOF_RDNSS_OPTION_BASE) {
          LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
          ("ND6_OPTION_TYPE_RDNSS: (optlen %"U16_F") is less than RDNSS BASE, packet dropped.\n",
              option_len));
          goto lenerr_drop_free_return;
        }
        break;
      }
#endif /* LWIP_ND6_RDNSS_MAX_DNS_SERVERS */
      default:
        /* 6.1.2.  Validation of Router Advertisement Messages
          The contents of any defined options that are not specified to be used
           with Router Advertisement messages MUST be ignored and the packet
           processed as normal.
        */
        break;
      }
      /* option length is checked earlier to be non-zero to make sure loop ends */
      offset = (u16_t)(offset + (u16_t)option_len);
    } /*END Validate options while loop.*/

   /* Offset to options. */
   offset = sizeof(struct ra_header);

   /* Process each option. */
   while ((p->tot_len - offset) >= 2) {
      int option_len8 = pbuf_try_get_at(p, (u16_t)(offset + 1));
      /*Checked for negative value already in Validation.*/

      if (p->len == p->tot_len) {
         /* no need to copy from contiguous pbuf */
         buffer = &((u8_t*)p->payload)[offset];
      }
      else {
      buffer = nd6_ra_buffer;

      if (((u16_t)option_len8 << 3) != pbuf_copy_partial(p, buffer, (u16_t)((u16_t)option_len8 << 3), offset)) {
          (void)pbuf_free(p);
          ND6_STATS_INC(nd6.lenerr);
          ND6_STATS_INC(nd6.drop);
          return;
        }
      }

      switch (buffer[0]) {
      case ND6_OPTION_TYPE_SOURCE_LLADDR:
      {
        struct lladdr_option *lladdr_opt;

        lladdr_opt = (struct lladdr_option *)buffer;
        if ((default_router_list[i].neighbor_entry != NULL) &&
            (default_router_list[i].neighbor_entry->state == ND6_INCOMPLETE)) {
          (void)memcpy_s(default_router_list[i].neighbor_entry->lladdr,inp->hwaddr_len, lladdr_opt->addr, inp->hwaddr_len);
          default_router_list[i].neighbor_entry->state = ND6_REACHABLE;
          default_router_list[i].neighbor_entry->counter.reachable_time = reachable_time;
        }
        break;
      }
      case ND6_OPTION_TYPE_MTU:
      {
        struct mtu_option *mtu_opt;
        u32_t mtu32;
        mtu_opt = (struct mtu_option *)buffer;
        mtu32   = lwip_htonl(mtu_opt->mtu);
        if ((mtu32 >= NETIF_MTU_MIN) && (mtu32 <= 0xffff)) {
#if LWIP_ND6_ALLOW_RA_UPDATES
          if (inp->mtu) {
            /* don't set the mtu for IPv6 higher than the netif driver supports */
            inp->mtu6 = (u16_t)LWIP_MIN(inp->mtu, (u16_t)mtu32);
          } else {
            inp->mtu6 = (u16_t)mtu32;
          }
#endif /* LWIP_ND6_ALLOW_RA_UPDATES */
        }
        break;
      }
      case ND6_OPTION_TYPE_PREFIX_INFO:
      {
        struct prefix_option *prefix_opt;
        u32_t preferred_lifetime;
        u32_t valid_lifetime;
        prefix_opt = (struct prefix_option *)buffer;
        preferred_lifetime = lwip_htonl(prefix_opt->preferred_lifetime);
        valid_lifetime =  lwip_htonl(prefix_opt->valid_lifetime);

        if ((prefix_opt->flags & ND6_PREFIX_FLAG_ON_LINK)
            && (LWIP_IPV6_PREFIX_LEN == prefix_opt->prefix_length)
            && !ip6_addr_islinklocal(&(prefix_opt->prefix))
            && !ip6_addr_isany(&(prefix_opt->prefix))) {
          /* Add to on-link prefix list. */
          s8_t prefix;
          ip6_addr_t prefix_addr;

          if (preferred_lifetime > valid_lifetime){
            LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
            ("Preferred lifetime(%"U32_F") is greater than valid lifetime(%"U32_F").\n",preferred_lifetime, valid_lifetime));
            break;
          }

          /* Get a memory-aligned copy of the prefix. */
          ip6_addr_set(&prefix_addr, &(prefix_opt->prefix));

          /* find cache entry for this prefix. */
          prefix = nd6_get_onlink_prefix(&prefix_addr, inp);

          /**
          @page RFC-4862 RFC-4862
          @par Compliant Sections
          Section 5.5.3.  Router Advertisement Processing>
          @par Behavior Description
          Test : If the received Valid Lifetime is greater than 2 hours or
                greater than RemainingLifetime, set the valid lifetime of the
                corresponding address to the advertised Valid Lifetime.
          */

          /* Updating Existing entry. */
          if (prefix >= 0) {
            if ((valid_lifetime > LWIP_TWO_HOURS)
            || (valid_lifetime > inp->prefix_list[prefix].invalidation_timer)) {
              inp->prefix_list[prefix].invalidation_timer = valid_lifetime;
            }
            /* 5.5.4. Address Lifetime Expiry
                *           A preferred address becomes deprecated when its preferred lifetime
                *           expires.
                */

            /* If RA has (preferred_lifetime > valid_lifetime) then discard */
            if (LWIP_IS_DEPRECATED_ENABLED(inp)
              && (preferred_lifetime > 0)
              && (inp->prefix_list[prefix].invalidation_timer > preferred_lifetime)) {

#if LWIP_IPV6_AUTOCONFIG
              /*If prefix is deprecated make all corespondig addr preferred again*/
              if (LWIP_IS_DEPRECATED(inp->prefix_list[prefix].invalidation_timer,
                inp->prefix_list[prefix].diff_preferred_lifetime)
                && (inp->prefix_list[prefix].flags & ND6_PREFIX_AUTOCONFIG_ADDRESS_GENERATED)) {
                /*If any address were configured with this prefix, make them
                preferred again.*/

                s8_t j;
                for (j = 1; j < LWIP_IPV6_NUM_ADDRESSES; j++) {
                  if ((netif_ip6_addr_state(inp, j) == IP6_ADDR_DEPRECATED)
                        && ip6_addr_netcmp(&prefix_addr, netif_ip6_addr(inp, j))) {
                    netif_ip6_addr_set_state(inp, j, IP6_ADDR_PREFERRED);
                    /*Exit loop*/
                    break;
                  }
                }
              }
#endif  /* LWIP_IPV6_AUTOCONFIG */

              /*reset preferred timer*/
              inp->prefix_list[prefix].diff_preferred_lifetime
                = inp->prefix_list[prefix].invalidation_timer - preferred_lifetime;
            }
          }
          else {
            /* Create a new cache entry. */
            prefix = nd6_new_onlink_prefix(&prefix_addr, inp);
            if (prefix >= 0) {
              /* Initializing prefix info */
              inp->prefix_list[prefix].invalidation_timer = valid_lifetime;
              inp->prefix_list[prefix].diff_preferred_lifetime = valid_lifetime - preferred_lifetime;
#if LWIP_IPV6_AUTOCONFIG
              if (prefix_opt->flags & ND6_PREFIX_FLAG_AUTONOMOUS) {
                /* Mark prefix as autonomous, so that address autoconfiguration can take place.
                 * Only OR flag, so that we don't over-write other flags (such as ADDRESS_DUPLICATE)*/
                inp->prefix_list[prefix].flags |= ND6_PREFIX_AUTOCONFIG_AUTONOMOUS;
              }
#endif /* LWIP_IPV6_AUTOCONFIG */
            } else {
               ND6_STATS_INC(nd6.cacheFull);
            }
          }
        }


        break;
      }
      case ND6_OPTION_TYPE_ROUTE_INFO:
        /* @todo implement preferred routes.
        struct route_option * route_opt;
        route_opt = (struct route_option *)buffer;*/

        break;
#if LWIP_ND6_RDNSS_MAX_DNS_SERVERS
      case ND6_OPTION_TYPE_RDNSS:
      {
        u8_t num, n;
        struct rdnss_option * rdnss_opt;

        if (option_len < SIZEOF_RDNSS_OPTION_BASE) {
          LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
          ("ND6_OPTION_TYPE_RDNSS: (optlen %"U16_F") is less than RDNSS BASE, packet dropped.\n",
              option_len));
          goto lenerr_drop_free_return;
        }

        rdnss_opt = (struct rdnss_option *)buffer;
        num = (rdnss_opt->length - 1) / 2;
        for (n = 0; (rdnss_server_idx < DNS_MAX_SERVERS) && (n < num); n++) {
          ip_addr_t rdnss_address;

          /* Get a memory-aligned copy of the prefix. */
          ip_addr_copy_from_ip6(rdnss_address, rdnss_opt->rdnss_address[n]);

          if (htonl(rdnss_opt->lifetime) > 0) {
            /* TODO implement Lifetime > 0 */
            dns_setserver(rdnss_server_idx++, &rdnss_address);
          } else {
            /* TODO implement DNS removal in dns.c */
            u8_t s;
            for (s = 0; s < DNS_MAX_SERVERS; s++) {
              const ip_addr_t *addr = dns_getserver(s);
              if(ip_addr_cmp(addr, &rdnss_address)) {
                dns_setserver(s, NULL);
              }
            }
          }
        }
        break;
      }
#endif /* LWIP_ND6_RDNSS_MAX_DNS_SERVERS */
      default:
        /* Unrecognized option, abort. */
        ND6_STATS_INC(nd6.proterr);
        break;
      }
      /* option length is checked earlier to be non-zero to make sure loop ends */
      offset = (u16_t)(offset + (u16_t)(((u16_t)buffer[1]) << 3));
    }
   break; /* ICMP6_TYPE_RA */
  }
  case ICMP6_TYPE_RD: /* Redirect */
  {
    struct redirect_header *redir_hdr = NULL;
    struct lladdr_option *lladdr_opt = NULL;
    ip6_addr_t tmp;
    ip6_addr_t tmpDest;
    ip6_addr_t destination_address;
    u8_t isrouter = 0;
    u8_t *buffer = NULL; /* Used to copy options. */
    u16_t offset;
    u8_t dup = 0;

    /* Check that Redir header fits in packet. */
    if (p->len < sizeof(struct redirect_header)) {
      /* @todo debug message */
      (void)pbuf_free(p);
      ND6_STATS_INC(nd6.lenerr);
      ND6_STATS_INC(nd6.drop);
      return;
    }

    redir_hdr = (struct redirect_header *)p->payload;

    if (0 != redir_hdr->code) {
        LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
          ("ICMPv6 RD header (code %"X8_F") is not zero, packet dropped.\n", redir_hdr->code));
        (void)pbuf_free(p);
        ND6_STATS_INC(nd6.drop);
        return;
    }

    /* [DTS2018052510354]- [TEST][lwIP][FUNC]: Codenomicon for ICMPv6 fails due to redirect.*/
    ip6_addr_set(&destination_address, &(redir_hdr->target_address));

    /* Check a subset of the other RFC 4861 Sec. 8.1 requirements. */
    if (!ip6_addr_islinklocal(ip6_current_src_addr()) ||
        IP6H_HOPLIM(ip6_current_header()) != ND6_HOPLIM ||
        redir_hdr->code != 0 || ip6_addr_ismulticast(&destination_address)) {
      (void)pbuf_free(p);
      ND6_STATS_INC(nd6.proterr);
      ND6_STATS_INC(nd6.drop);
      return;
    }

    /**
    @page RFC-4861 RFC-4861
    @par Compliant Section
    Section 4.5.  Redirect Message Format
    @par Behavior Description
    An IP address that is a better first hop to use for the ICMP Destination Address.
    When the target is the actual endpoint of communication, i.e., the destination is a neighbor,
    the Target Address field MUST contain the same value as the ICMP Destination Address field.
    Otherwise, the target is a better first-hop router and the Target Address MUST be the router's
    link-local address so that hosts can uniquely identify routers.
    Behavior:  Silently discard any received Router Advertisement
    */
    /**
    @page RFC-4861 RFC-4861
    @par Compliant Sections
    Section 8.1.  Validation of Redirect Messages
    @par Behavior Description
    The ICMP Target Address is either a link-local address (when redirected to a router) or
    the same as the ICMP Destination Address (when redirected to the on-link destination.
    Behavior:  Silently discard any received Router Advertisement
    */
    ip6_addr_set(&tmp, &(redir_hdr->target_address));
    ip6_addr_set(&tmpDest, &(redir_hdr->destination_address));
    if (!ip6_addr_cmp(&(tmp), &(tmpDest)))
    {
        if (!ip6_addr_islinklocal(&tmp))
        {
            LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
              ("ICMPv6 RD header target address is not link local address .\n"));
            (void)pbuf_free(p);
            ND6_STATS_INC(nd6.proterr);
            ND6_STATS_INC(nd6.drop);
            return;
        }
        isrouter = 1;
    }



    if (0 > nd6_validate_options((u8_t*)p->payload + sizeof(struct redirect_header),
                                                        (int) (p->len - (sizeof(struct redirect_header))))){
        (void)pbuf_free(p);
        ND6_STATS_INC(nd6.lenerr);
        ND6_STATS_INC(nd6.drop);
        return;
    }


    /* Offset to options. */
    offset = sizeof(struct redirect_header);

    /* Process each option. */
    while ((p->tot_len - offset) >= 2) {
      u16_t option_len;
      int opt_type;
      int option_len8 = pbuf_try_get_at(p, (u16_t)(offset + 1));
      if (option_len8 <= 0) {
        LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
          ("IPv6 options header (optlen %"S32_F") is not specified, packet dropped.\n",
              option_len8));
        /* read beyond end or zero length */
        goto lenerr_drop_free_return;
      }
      option_len = (u16_t)(((u8_t)option_len8) << 3);
      if (option_len > p->tot_len - offset) {
        LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
          ("IPv6 options header (optlen %"S32_F") does not fit in (total len %"U16_F") and (offset %"U16_F"), packet dropped.\n",
              option_len8, p->tot_len, offset));
        /* short packet (option does not fit in) */
        goto lenerr_drop_free_return;
      }

      opt_type =  pbuf_try_get_at(p, offset);
      LWIP_ASSERT("opt_type >= 0", opt_type >= 0);

      switch (opt_type) {
        case ND6_OPTION_TYPE_TARGET_LLADDR:
          if (option_len != sizeof(struct lladdr_option)) {
            LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
            ("ND6_OPTION_TYPE_TARGET_LLADDR: (optlen %"U16_F") is less than struct lladdr_option, packet dropped.\n",
                option_len));
            goto lenerr_drop_free_return;
          }
          break;

        case ND6_OPTION_TYPE_REDIRECT_HEADER:
           LWIP_DEBUGF (IP6_DEBUG | LWIP_DBG_LEVEL_WARNING,
            ("ND6_OPTION_TYPE_REDIRECT_HEADER: option not supported so skipping .\n"));
            break;

        default:
            ND6_STATS_INC(nd6.proterr);
            break;
        }/* End of Switch */

      offset = (u16_t)(offset + (u16_t)option_len);
    } /*End of While */

    /* [DTS2018052510354]- [TEST][lwIP][FUNC]: Codenomicon for ICMPv6 fails due to redirect.*/
    /* check if target address is not same as stack's address, it will create loopback, treat such RD packet as invalid
      packets and ignore thema.
    */
    ip6_addr_set(&tmp, &(redir_hdr->target_address));
    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; ++i) {
      if (!ip6_addr_isinvalid(netif_ip6_addr_state(inp, i)) &&
          ip6_addr_cmp(&tmp, netif_ip6_addr(inp, i))) {
          /* Not a valid message. */
          (void)pbuf_free(p);
          ND6_STATS_INC(nd6.proterr);
          ND6_STATS_INC(nd6.drop);
          return;
      }
    }

    /* Copy original destination address to current source address, to have an aligned copy. */
    ip6_addr_set(&tmp, &(redir_hdr->destination_address));

    /* Find dest address in cache */
    i = nd6_find_destination_cache_entry(&tmp);
    if (i < 0) {
      /* Destination not in cache, drop packet. */
      (void)pbuf_free(p);
      ND6_STATS_INC(nd6.drop);
      return;
    }
    /**
    @page RFC-4861 RFC-4861
    @par Compliant Sections
    Section 8.1. Validation of Redirect Messages
    @par Behavior Description
    The IP source address of the Redirect is the same as the current first-hop router for the specified
     ICMP Destination Address. Behavior:Silently discard any received Router Advertisement
    */
    if (!ip6_addr_cmp(ip6_current_src_addr(),&(destination_cache[i].next_hop_addr)))
    {
      LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
              ("ICMPv6 RD msg,IP source addr is not same as the dest first-hop/next-hop addr.\n"));
      (void)pbuf_free(p);
      ND6_STATS_INC(nd6.proterr);
      ND6_STATS_INC(nd6.drop);
      return;
    }

    /* Set the new target address. */
    ip6_addr_set(&(destination_cache[i].next_hop_addr), &(redir_hdr->target_address));

    /* Offset to options. */
    offset = sizeof(struct redirect_header);

    /* Process each option. */
    while ((p->tot_len - offset) >= 2) {

      if (p->len == p->tot_len) {
        /* no need to copy from contiguous pbuf */
         buffer = &((u8_t*)p->payload)[offset];
      } else {
        buffer = rd6_ra_buffer;
        /* already validation is done at the above loop so now just perform buffer parsing */
        (void)pbuf_copy_partial(p, buffer, sizeof(struct lladdr_option), offset);

      }

      switch (buffer[0]) {

      case ND6_OPTION_TYPE_TARGET_LLADDR:
        if (dup){
          LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_WARNING,
              ("ND6_OPTION_TYPE_TARGET_LLADDR: option is present repeatedly so taking only the first and ignoring the next items \n"));
          break;
        }
        else {
          dup = 1; /* to avoid updating repeatedly.*/
        }
        lladdr_opt = (struct lladdr_option *)buffer;

        /* If Link-layer address of other router is given, try to add to neighbor cache. */
        if (lladdr_opt != NULL) {

          /* Copy target address to current source address, to have an aligned copy. */
          ip6_addr_set(&tmp, &(redir_hdr->target_address));

          i = nd6_find_neighbor_cache_entry(&tmp, inp);
          if (i < 0) {
            i = nd6_new_neighbor_cache_entry();
            if (i >= 0) {
              neighbor_cache[i].netif = inp;
              (void)memcpy_s(neighbor_cache[i].lladdr, NETIF_MAX_HWADDR_LEN, lladdr_opt->addr, inp->hwaddr_len);
              ip6_addr_set_val(&(neighbor_cache[i].next_hop_address), &tmp);

              /* Receiving a message does not prove reachability: only in one direction.
               * Delay probe in case we get confirmation of reachability from upper layer (TCP). */
              neighbor_cache[i].state = ND6_DELAY;
              neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME / ND6_TMR_INTERVAL;
            }
          } else {
            if (neighbor_cache[i].state == ND6_INCOMPLETE)
            {
              (void)memcpy_s(neighbor_cache[i].lladdr, NETIF_MAX_HWADDR_LEN, lladdr_opt->addr, inp->hwaddr_len);
              /* Receiving a message does not prove reachability: only in one direction.
               * Delay probe in case we get confirmation of reachability from upper layer (TCP). */
              neighbor_cache[i].state = ND6_DELAY;
              neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME / ND6_TMR_INTERVAL;
            }
            else if (neighbor_cache[i].state > ND6_INCOMPLETE)
            {
              if (!lwip_lladdr_cmp(neighbor_cache[i].lladdr,lladdr_opt->addr))
              {
                (void)memcpy_s(neighbor_cache[i].lladdr, NETIF_MAX_HWADDR_LEN, lladdr_opt->addr, inp->hwaddr_len);
                neighbor_cache[i].state = ND6_STALE;
                neighbor_cache[i].counter.stale_time = 0;
              }
              if (isrouter) {
                 neighbor_cache[i].isrouter = isrouter;
              }
            }
          }
        }
        break; /* ICMP6_TYPE_RD */

      case ND6_OPTION_TYPE_REDIRECT_HEADER:
         LWIP_DEBUGF (IP6_DEBUG | LWIP_DBG_LEVEL_WARNING,
          ("ND6_OPTION_TYPE_REDIRECT_HEADER: option not supported so skipping .\n"));
          break;

      default:
          ND6_STATS_INC(nd6.proterr);
          break;
      }/* End of Switch */
      offset = (u16_t)(offset + (u16_t)(((u16_t)buffer[1]) << 3));
    } /*End of While */
    break;
  }
  case ICMP6_TYPE_PTB: /* Packet too big */
  {
    struct icmpv6_hdr *icmp6hdr; /* Packet too big message */
    struct ip6_hdr *ip6hdr; /* IPv6 header of the packet which caused the error */
    u32_t pmtu;
    ip_addr_t destination_address;

    /* Check that ICMPv6 header + IPv6 header fit in payload */
    if (p->len < (sizeof(struct icmpv6_hdr)) + IP6_HLEN) {
      /* drop short packets */
      (void)pbuf_free(p);
      ND6_STATS_INC(nd6.lenerr);
      ND6_STATS_INC(nd6.drop);
      return;
    }

    icmp6hdr = (struct icmpv6_hdr *)p->payload;
    ip6hdr = (struct ip6_hdr *)((u8_t*)p->payload + sizeof(struct icmp6_hdr));

    ip_addr_copy_from_ip6(destination_address, ip6hdr->dest);
    /* Look for entry in destination cache. */
    i = nd6_find_destination_cache_entry(&destination_address.u_addr.ip6);
    if (i < 0) {
      /* Destination not in cache, drop packet. */
      (void)pbuf_free(p);
      return;
    }
    /**
     @page RFC-2460 RFC-2460

     @par Compliant Section
     Section 5. Packet Size Issues
     @par Behavior Description
     IPv6 requires that every link in the internet have an MTU of 1280 octets or greater.  On any link that cannot convey a 1280-octet
      packet in one piece, link-specific fragmentation and reassembly must be provided at a layer below IPv6.
     */
    /* Change the Path MTU. */
    pmtu = lwip_htonl(icmp6hdr->data);
    if (pmtu < NETIF_MTU_MIN) {
      destination_cache[i].pmtu = NETIF_MTU_MIN;
    }
    else
    {
      destination_cache[i].pmtu = (u16_t)LWIP_MIN(pmtu, 0xFFFF);
    }

    break; /* ICMP6_TYPE_PTB */
  }

  default:
    ND6_STATS_INC(nd6.proterr);
    ND6_STATS_INC(nd6.drop);
    break; /* default */
  }

  (void)pbuf_free(p);
  return;
lenerr_drop_free_return:
  ND6_STATS_INC(nd6.lenerr);
  ND6_STATS_INC(nd6.drop);
  (void)pbuf_free(p);
}

/**
      @page RFC-4862 RFC-4862
      @par Non-Compliant Section
      Section 5.1  Node Configuration Variables
      @par Behavior Description
      RetransTimer allows the user to change time delay between consecutive
      Neighbor Solicitation during DAD, but in the project it depends on
      nd6_tmr() interval.
*/

/**
      @page RFC-4862 RFC-4862
      @par Non-Compliant Section
      Section 5.4.2 (c) (d) Sending Neighbor Solicitation Messages
      @par Behavior Description
      (c) If Neighbor Solicitation is the first outgoing message after
      re-initialization then node should delay the joining solicited-node
      multicast address, project does not put any delay in this case.
      (d) The node should delay joining the solicited-node
           multicast address by a random delay If the address being checked is configured
           by a router advertisement message sent to a multicast address,
           project does not put any delay in this case.
*/

/**
      @page RFC-4862 RFC-4862
      @par Non-Compliant Section
      Section 5.5.3 (d)  Router Advertisement Processing
      @par Behavior Description
      (d) If prefix advertised is not equal to the prefix of an address
      configured by stateless autoconfiguration form address by rule
      @verbatim
      |---128 - N bits---|----N bits --|
      +---------------+----------+
      |----prefix-------|---Identifier-|
      @endverbatim
      , but project is supporting 64 bit prefixes.
*/

/**
      @page RFC-4862 RFC-4862
      @par Non-Compliant Section
      Section 5.6 Retaining Configured Address for Stability. 6. Security
      Considerations
      @par Behavior Description
      Retaining address after reboot is not supported.
      Secure Neighbor Discovery is not supported.

*/

/*
 * Periodic timer for Neighbor discovery functions:
 *
 * - Update neighbor reachability states
 * - Update destination cache entries age
 * - Update invalidation timers of default routers and on-link prefixes
 * - Update preferred timers of on-link prefixes.
 * - Perform duplicate address detection (DAD) for our addresses
 * - Send router solicitations
 */
void
nd6_tmr(void)
{
  s8_t i;
  struct netif *netif;

  /* Process neighbor entries. */
  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
    switch (neighbor_cache[i].state) {
    case ND6_INCOMPLETE:
      if ((neighbor_cache[i].counter.probes_sent >= LWIP_ND6_MAX_MULTICAST_SOLICIT) &&
          (!neighbor_cache[i].isrouter)) {
        /* Retries exceeded. */
        nd6_free_neighbor_cache_entry(i);
      } else {
        /* Send a NS for this entry. */
        neighbor_cache[i].counter.probes_sent++;
        nd6_send_neighbor_cache_probe(&neighbor_cache[i], ND6_SEND_FLAG_MULTICAST_DEST);
      }
      break;
    case ND6_REACHABLE:
      /* Send queued packets, if any are left. Should have been sent already. */
      if (neighbor_cache[i].q != NULL) {
        nd6_send_q(i);
      }
      if (neighbor_cache[i].counter.reachable_time <= ND6_TMR_INTERVAL) {
        /* Change to stale state. */
        neighbor_cache[i].state = ND6_STALE;
        neighbor_cache[i].counter.stale_time = 0;
      } else {
        neighbor_cache[i].counter.reachable_time -= ND6_TMR_INTERVAL;
      }
      break;
    case ND6_STALE:
      neighbor_cache[i].counter.stale_time++;
      break;
    case ND6_DELAY:
      if (neighbor_cache[i].counter.delay_time <= 1) {
        /* Change to PROBE state. */
        neighbor_cache[i].state = ND6_PROBE;
        neighbor_cache[i].counter.probes_sent = 0;
      } else {
        neighbor_cache[i].counter.delay_time--;
      }
      break;
    case ND6_PROBE:
      if ((neighbor_cache[i].counter.probes_sent >= LWIP_ND6_MAX_MULTICAST_SOLICIT) &&
          (!neighbor_cache[i].isrouter)) {
        /* Retries exceeded. */
        nd6_free_neighbor_cache_entry(i);
      } else {
        /* Send a NS for this entry. */
        neighbor_cache[i].counter.probes_sent++;
        nd6_send_neighbor_cache_probe(&neighbor_cache[i], 0);
      }
      break;
    case ND6_NO_ENTRY:
    default:
      /* Do nothing. */
      break;
    }
  }

  /* Process destination entries. */
  for (i = 0; i < LWIP_ND6_NUM_DESTINATIONS; i++) {
    destination_cache[i].age++;
  }

  /* Process router entries. */
  for (i = 0; i < LWIP_ND6_NUM_ROUTERS; i++) {
    if (default_router_list[i].neighbor_entry != NULL) {
      /* Active entry. */
      if (default_router_list[i].invalidation_timer <= ND6_TMR_INTERVAL / 1000) {
        /* No more than 1 second remaining. Clear this entry. Also clear any of
         * its destination cache entries, as per RFC 4861 Sec. 5.3 and 6.3.5. */
        s8_t j;
        for (j = 0; j < LWIP_ND6_NUM_DESTINATIONS; j++) {
          if (ip6_addr_cmp(&destination_cache[j].next_hop_addr,
               &default_router_list[i].neighbor_entry->next_hop_address)) {
             ip6_addr_set_any(&destination_cache[j].destination_addr);
          }
        }
        default_router_list[i].neighbor_entry->isrouter = 0;
        default_router_list[i].neighbor_entry = NULL;
        default_router_list[i].invalidation_timer = 0;
        default_router_list[i].flags = 0;
      } else {
        default_router_list[i].invalidation_timer -= ND6_TMR_INTERVAL / 1000;
      }
    }
  }

  /*Process prefix entries. */
  for (netif = netif_list; netif != NULL; netif = netif->next) {
      for (i = 0; i < LWIP_ND6_MAX_PREFIX_ON_INTERFACE; i++) {
          if (ip6_addr_isany(&netif->prefix_list[i].prefix)){
            continue;
          }
          if (netif->prefix_list[i].invalidation_timer < ND6_TMR_INTERVAL / 1000) {
              /*Entry timed out , remove it */
              netif->prefix_list[i].invalidation_timer = 0;
#if LWIP_IPV6_AUTOCONFIG
              /*If any address were configured with this prefix, remove them.*/
              if (netif->prefix_list[i].flags & ND6_PREFIX_AUTOCONFIG_ADDRESS_GENERATED) {
                s8_t j;
                for (j = 1; j < LWIP_IPV6_NUM_ADDRESSES; j++) {
                    if ((netif_ip6_addr_state(netif, j) != IP6_ADDR_INVALID)
                            && ip6_addr_netcmp(&netif->prefix_list[i].prefix, netif_ip6_addr(netif, j))) {
                        netif_ip6_addr_set_state(netif, j, IP6_ADDR_INVALID);
                        netif->prefix_list[i].flags = 0;

                        /*Exit loop*/
                        break;
                    }
                }
              }
#endif  /* LWIP_IPV6_AUTOCONFIG */
              netif->prefix_list[i].flags = 0;
          } else {

#if LWIP_IPV6_AUTOCONFIG
       /**
             @page RFC-4862 RFC-4862
             @par Compliant Section
             Section 5.5.4.  Address Lifetime Expiry
             @par Behavior Description
             A preferred address becomes deprecated when its preferred lifetime expires,
             and address state changes to invalid after expiration of valid-lifetime.
             */

              if (LWIP_IS_DEPRECATED_ENABLED(netif)
                      && LWIP_IS_DEPRECATED(netif->prefix_list[i].invalidation_timer,
                          netif->prefix_list[i].diff_preferred_lifetime)
                      && netif->prefix_list[i].flags & ND6_PREFIX_AUTOCONFIG_ADDRESS_GENERATED) {
                  s8_t j;

                  for (j = 1; j < LWIP_IPV6_NUM_ADDRESSES; j++) {
                      if ((netif_ip6_addr_state(netif,j) == IP6_ADDR_PREFERRED)
                              && ip6_addr_netcmp(&netif->prefix_list[i].prefix, netif_ip6_addr(netif,j))) {
                          netif_ip6_addr_set_state(netif, j, IP6_ADDR_DEPRECATED);
                          /* Exit loop */
                          break;
                      }
                  }
              }
#endif /* LWIP_IPV6_AUTOCONFIG */

#if LWIP_ADDR_EXPIRATION_ALLOWED
              netif->prefix_list[i].invalidation_timer -= ND6_TMR_INTERVAL /1000;
#endif

#if LWIP_IPV6_AUTOCONFIG
              /* Initiate address autoconfiguration for this prefix, if conditions are met. */
              if (netif->ip6_autoconfig_enabled &&
                      (netif->prefix_list[i].flags & ND6_PREFIX_AUTOCONFIG_AUTONOMOUS) &&
                      !(netif->prefix_list[i].flags & ND6_PREFIX_AUTOCONFIG_ADDRESS_GENERATED)) {
                  s8_t j;
                  /* Try to get an address on this netif that is invalid.
                   * * Skip 0 index (link-local address) */
                  for (j = 1; j < LWIP_IPV6_NUM_ADDRESSES; j++) {
                      if (netif_ip6_addr_state(netif, j) == IP6_ADDR_INVALID) {
                          /* Generate an address using this prefix and interface ID from link-local address. */
                          netif_ip6_addr_set_parts(netif, j,
                          netif->prefix_list[i].prefix.addr[0], netif->prefix_list[i].prefix.addr[1],
                          netif_ip6_addr(netif, 0)->addr[2], netif_ip6_addr(netif, 0)->addr[3]);

                          /* Mark it as tentative (DAD will be performed if configured). */
                          if (LWIP_IS_DAD_ENABLED(netif)) {
                            netif_ip6_addr_set_state(netif, j, IP6_ADDR_TENTATIVE);
                          }else{
                            netif_ip6_addr_set_state(netif, j, IP6_ADDR_PREFERRED);
                          }

                          /* Mark this prefix with ADDRESS_GENERATED, so that we don't try again. */
                          netif->prefix_list[i].flags |= ND6_PREFIX_AUTOCONFIG_ADDRESS_GENERATED;

                          /* Exit loop. */
                          break;
                      }
                  }
              }
#endif /* LWIP_IPV6_AUTOCONFIG */
          }
      }
  } /*END Process prefix entries. for loop*/

  /* Process our own addresses, if DAD configured. */
  for (netif = netif_list; netif != NULL; netif = netif->next) {

    /* Do not send NS/NA/RS/RA packets to loopback interface */
    if (netif->flags & NETIF_FLAG_LOOPBACK){
      continue;
    }

    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; ++i) {
      u8_t addr_state = netif_ip6_addr_state(netif, i);
      if (ip6_addr_istentative(addr_state)) {
#if LWIP_IPV6_DUP_DETECT_ATTEMPTS
        if (!LWIP_IS_DAD_ENABLED(netif)) {
            netif_ip6_addr_set_state(netif, i, IP6_ADDR_PREFERRED);
            continue;
        }

        if ((addr_state & IP6_ADDR_TENTATIVE_COUNT_MASK) >= LWIP_IPV6_DUP_DETECT_ATTEMPTS) {
          /* No NA received in response. Mark address as valid. */
            netif_ip6_addr_set_state(netif, i, IP6_ADDR_PREFERRED);
        } else if (netif->flags & NETIF_FLAG_UP) {
          /* Send a NS for this address. */
          nd6_send_ns(netif, netif_ip6_addr(netif, i), ND6_SEND_FLAG_MULTICAST_DEST|ND6_SEND_FLAG_UNSPEC_SRC);
          /* tentative: set next state by increasing by one */
          netif_ip6_addr_set_state(netif, i, (u8_t)(addr_state + 1));
        }
#else
        netif_ip6_addr_set_state(netif, i, IP6_ADDR_PREFERRED);
#endif
      }
    }
  }

#if LWIP_IPV6_SEND_ROUTER_SOLICIT
  if (!nd6_tmr_rs_reduction) {
    nd6_tmr_rs_reduction = (ND6_RTR_SOLICITATION_INTERVAL / ND6_TMR_INTERVAL) - 1;
    /* Send router solicitation messages, if necessary. */
    for (netif = netif_list; netif != NULL; netif = netif->next) {
      /* Do not send NS/NA/RS/RA packets to loopback interface */
      if (netif->flags & NETIF_FLAG_LOOPBACK){
        continue;
      }

      if ((netif->rs_count > 0) && (netif->flags & NETIF_FLAG_UP) &&
          (!ip6_addr_isinvalid(netif_ip6_addr_state(netif, 0)))) {
        if (nd6_send_rs(netif) == ERR_OK) {
          netif->rs_count--;
        }
      }
    }
  }else{
    nd6_tmr_rs_reduction--;
  }
#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */

#if LWIP_ICMP6_ERR_RT_LMT
  icmp6_err_rate_calc();
#endif /* LWIP_ICMP6_ERR_RT_LMT */


}

/* Send a neighbor solicitation message for a specific neighbor cache entry
 *
 * @param entry the neightbor cache entry for wich to send the message
 * @param flags one of ND6_SEND_FLAG_*
 */
static void
nd6_send_neighbor_cache_probe(struct nd6_neighbor_cache_entry *entry, u8_t flags)
{
  nd6_send_ns(entry->netif, &entry->next_hop_address, flags);
}

/*
 * Send a neighbor solicitation message
 *
 * @param netif the netif on which to send the message
 * @param target_addr the IPv6 target address for the ND message
 * @param flags one of ND6_SEND_FLAG_*
 */
static void
nd6_send_ns(struct netif *netif, const ip6_addr_t *target_addr, u8_t flags)
{
  struct ns_header *ns_hdr = NULL;
  struct pbuf *p = NULL;
  const ip6_addr_t *src_addr = NULL;
  u16_t lladdr_opt_len;

  /* RFC4862 section 7.7.2: If the source address of the packet prompting the solicitation is the same
    as one of the addresses assigned to the outgoing interface, that address SHOULD be placed in the IP
    Source Address of the outgoing solicitation. Otherwise, any one of the addresses assigned to the
    interface should be used. Using the prompting packet's source address when possible ensures that
    the recipient of the Neighbor Solicitation installs in its Neighbor Cache the IP address that is highly
    likely to be used in subsequent return traffic belonging to the prompting packet's "connection". */

  /* lwip does NOT comply with this specification as the source address of NA is choosen based on the
    target address. In most of cases, if users don't declare the specific source address by binding socket to
    one specific address, the source address of IPV6 packets is also choosen based on target address, so the
    source address of NS is same as the prompting IPv6 packet. */

  if (!(flags & ND6_SEND_FLAG_UNSPEC_SRC)) {
    src_addr = ip_2_ip6(ip6_select_source_address(netif, target_addr));
    if (src_addr == NULL) {
      int j;
      for (j = 0; j < LWIP_IPV6_NUM_ADDRESSES; j++) {
        if (ip6_addr_isvalid(netif_ip6_addr_state(netif, j))) {
          src_addr = netif_ip6_addr(netif, j);
          break;
        }
      }

      if (j == LWIP_IPV6_NUM_ADDRESSES) {
        LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_WARNING, ("ICMPv6 NS: no available src address\n"));
        ND6_STATS_INC(nd6.err);
        return;
      }
    }
    /* calculate option length (in 8-byte-blocks) */
    lladdr_opt_len = (u16_t)(((netif->hwaddr_len + 2) + 7) >> 3);
  } else if (flags & ND6_SEND_FLAG_UNSPEC_SRC){
    src_addr = IP6_ADDR_ANY6;
    /* Option "MUST NOT be included when the source IP address is the unspecified address." */
    lladdr_opt_len = 0;
  } else {
    LWIP_DEBUGF(IP6_DEBUG | LWIP_DBG_LEVEL_WARNING, ("ICMPv6 NS: Not send because LLA state %"X8_F" with flag %"X8_F"\n",
                                                  netif_ip6_addr_state(netif,0), flags));
    ND6_STATS_INC(nd6.err);
    return;
  }

  /* Allocate a packet. */
  p = pbuf_alloc(PBUF_IP, (u16_t)(sizeof(struct ns_header) + (unsigned int)(lladdr_opt_len << 3)), PBUF_RAM);
  if (p == NULL) {
    ND6_STATS_INC(nd6.memerr);
    return;
  }

  /* Set fields. */
  ns_hdr = (struct ns_header *)p->payload;

  ns_hdr->type = ICMP6_TYPE_NS;
  ns_hdr->code = 0;
  ns_hdr->chksum = 0;
  ns_hdr->reserved = 0;
  ip6_addr_set_val(&(ns_hdr->target_address), target_addr);

  if (lladdr_opt_len != 0) {
    struct lladdr_option *lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct ns_header));
    lladdr_opt->type = ND6_OPTION_TYPE_SOURCE_LLADDR;
    lladdr_opt->length = (u8_t)lladdr_opt_len;
    (void)memcpy_s(lladdr_opt->addr,netif->hwaddr_len,netif->hwaddr, netif->hwaddr_len);
  }

  /* Generate the solicited node address for the target address. */
  if (flags & ND6_SEND_FLAG_MULTICAST_DEST) {
    ip6_addr_set_solicitednode(&multicast_address, target_addr->addr[3]);
    target_addr = &multicast_address;
  }

#if CHECKSUM_GEN_ICMP6
  IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_ICMP6) {
    ns_hdr->chksum = ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->len, src_addr,
      target_addr);
  }
#endif /* CHECKSUM_GEN_ICMP6 */

  /* Send the packet out. */
  ND6_STATS_INC(nd6.xmit);
  (void)ip6_output_if(p, (src_addr == IP6_ADDR_ANY6) ? NULL : src_addr, target_addr,
      ND6_HOPLIM, 0, IP6_NEXTH_ICMP6, netif);
  (void)pbuf_free(p);
}

/*
 * Send a neighbor advertisement message
 *
 * @param netif the netif on which to send the message
 * @param target_addr the IPv6 target address for the ND message
 * @param flags one of ND6_SEND_FLAG_*
 */
void
nd6_send_na(struct netif *netif, const ip6_addr_t *target_addr, u8_t flags)
{
  struct na_header *na_hdr = NULL;
  struct lladdr_option *lladdr_opt = NULL;
  struct pbuf *p = NULL;
  const ip6_addr_t *src_addr = NULL;
  const ip6_addr_t *dest_addr = NULL;
  u16_t lladdr_opt_len;

  /* Use target address as source address. */
  src_addr = target_addr;

  /* Allocate a packet. */
  lladdr_opt_len = (u16_t)(((netif->hwaddr_len + 2) >> 3) + (((netif->hwaddr_len + 2) & 0x07) ? 1 : 0));
  p = pbuf_alloc(PBUF_IP, (u16_t)(sizeof(struct na_header) + (unsigned int)(lladdr_opt_len << 3)), PBUF_RAM);
  if (p == NULL) {
    ND6_STATS_INC(nd6.memerr);
    return;
  }

  /* Set fields. */
  na_hdr = (struct na_header *)p->payload;
  lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct na_header));

  na_hdr->type = ICMP6_TYPE_NA;
  na_hdr->code = 0;
  na_hdr->chksum = 0;
  na_hdr->flags = flags & 0xf0;
  na_hdr->reserved[0] = 0;
  na_hdr->reserved[1] = 0;
  na_hdr->reserved[2] = 0;
  ip6_addr_set_val(&(na_hdr->target_address), target_addr);

  lladdr_opt->type = ND6_OPTION_TYPE_TARGET_LLADDR;
  lladdr_opt->length = (u8_t)lladdr_opt_len;
  (void)memcpy_s(lladdr_opt->addr, netif->hwaddr_len, netif->hwaddr, netif->hwaddr_len);

  /* Generate the solicited node address for the target address. */
  if (flags & ND6_SEND_FLAG_MULTICAST_DEST) {
    ip6_addr_set_solicitednode(&multicast_address, target_addr->addr[3]);
    dest_addr = &multicast_address;
  } else if (flags & ND6_SEND_FLAG_ALLNODES_DEST) {
    ip6_addr_set_allnodes_linklocal(&multicast_address);
    dest_addr = &multicast_address;
  } else {
    dest_addr = ip6_current_src_addr();
  }

#if CHECKSUM_GEN_ICMP6
  IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_ICMP6) {
    na_hdr->chksum = ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->len, src_addr,
      dest_addr);
  }
#endif /* CHECKSUM_GEN_ICMP6 */

  /* Send the packet out. */
  ND6_STATS_INC(nd6.xmit);
  (void)ip6_output_if(p, src_addr, dest_addr,
      ND6_HOPLIM, 0, IP6_NEXTH_ICMP6, netif);
  (void)pbuf_free(p);
}

#if LWIP_IPV6_SEND_ROUTER_SOLICIT
/*
 * Send a router solicitation message
 *
 * @param netif the netif on which to send the message
 *
 * RFC-COMPLIANCE-LIMITATION:
 * [ [RFC 4861 6.3.7]
 * [Test v6LC.1.2.8: Test v6LC.2.2.1: Router Solicitations (Hosts Only)]
 * [Compliant]
 * [Behavior: Our node conforms to RFC4861.
 * Router Solicitations may be sent after any of the following events
 * - The interface is initialized at system startup time.
 * - The interface is reinitialized after a temporary interface
 *   failure or after being temporarily disabled by system
 *   management.
 * - The host attaches to a link for the first time.
 * - The host re-attaches to a link after being detached for some
 *   time.
 * ] ]
 */
err_t
nd6_send_rs(struct netif *netif)
{
  struct rs_header *rs_hdr = NULL;
  struct lladdr_option *lladdr_opt = NULL;
  struct pbuf *p = NULL;
  const ip6_addr_t *src_addr = NULL;
  err_t err;
  u16_t lladdr_opt_len = 0;

  /* Link-local source address, or unspecified address? */
  if (ip6_addr_isvalid(netif_ip6_addr_state(netif, 0))) {
    src_addr = netif_ip6_addr(netif, 0);
  } else {
    src_addr = IP6_ADDR_ANY6;
  }

  /* Generate the all routers target address. */
  ip6_addr_set_allrouters_linklocal(&multicast_address);

  /* Allocate a packet. */
  if (src_addr != IP6_ADDR_ANY6) {
    lladdr_opt_len = (u16_t)(((netif->hwaddr_len + 2) >> 3) + (((netif->hwaddr_len + 2) & 0x07) ? 1 : 0));
  }
  p = pbuf_alloc(PBUF_IP, (u16_t)(sizeof(struct rs_header) + (unsigned int)(lladdr_opt_len << 3)), PBUF_RAM);
  if (p == NULL) {
    ND6_STATS_INC(nd6.memerr);
    return ERR_BUF;
  }

  /* Set fields. */
  rs_hdr = (struct rs_header *)p->payload;

  rs_hdr->type = ICMP6_TYPE_RS;
  rs_hdr->code = 0;
  rs_hdr->chksum = 0;
  rs_hdr->reserved = 0;

  if (src_addr != IP6_ADDR_ANY6) {
    /* Include our hw address. */
    lladdr_opt = (struct lladdr_option *)((u8_t*)p->payload + sizeof(struct rs_header));
    lladdr_opt->type = ND6_OPTION_TYPE_SOURCE_LLADDR;
    lladdr_opt->length = (u8_t)lladdr_opt_len;
    (void)memcpy_s(lladdr_opt->addr, netif->hwaddr_len, netif->hwaddr, netif->hwaddr_len);
  }

#if CHECKSUM_GEN_ICMP6
  IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_ICMP6) {
    rs_hdr->chksum = ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->len, src_addr,
      &multicast_address);
  }
#endif /* CHECKSUM_GEN_ICMP6 */

  /* Send the packet out. */
  ND6_STATS_INC(nd6.xmit);

  err = ip6_output_if(p, (src_addr == IP6_ADDR_ANY6) ? NULL : src_addr, &multicast_address,
      ND6_HOPLIM, 0, IP6_NEXTH_ICMP6, netif);
  (void)pbuf_free(p);

  return err;
}
#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */


err_t
nd6_add_neighbor_cache_entry(struct netif *netif, const ip6_addr_t *nbr_addr,const u8_t *addr,const u8_t addrlen)
{
  int idx;

  idx = nd6_find_neighbor_cache_entry(nbr_addr, netif);
  if (idx>= 0) {

    /* We already have a record for the solicitor. */
    if (neighbor_cache[idx].state == ND6_INCOMPLETE) {
      neighbor_cache[idx].netif = netif;
      (void)memcpy_s(neighbor_cache[idx].lladdr, NETIF_MAX_HWADDR_LEN, addr, addrlen);

      /* Delay probe in case we get confirmation of reachability from upper layer (TCP). */
      neighbor_cache[idx].state = ND6_DELAY;
      neighbor_cache[idx].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME / ND6_TMR_INTERVAL;
    }
  } else {
    /* Add their IPv6 address and link-layer address to neighbor cache.
     * We will need it at least to send a unicast NA message, but most
     * likely we will also be communicating with this node soon. */
    idx = nd6_new_neighbor_cache_entry();
    if (idx < 0) {
      /* We couldn't assign a cache entry for this neighbor.
       * we won't be able to reply. drop it. */
      ND6_STATS_INC(nd6.cacheFull);
      return ERR_MEM;
    }
    neighbor_cache[idx].netif = netif;
    (void)memcpy_s(neighbor_cache[idx].lladdr, NETIF_MAX_HWADDR_LEN, addr, addrlen);
    ip6_addr_set(&(neighbor_cache[idx].next_hop_address), nbr_addr);

    /* Receiving a message does not prove reachability: only in one direction.
     * Delay probe in case we get confirmation of reachability from upper layer (TCP). */
    neighbor_cache[idx].state = ND6_DELAY;
    neighbor_cache[idx].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME / ND6_TMR_INTERVAL;
  }

  return ERR_OK;
}

/*
 * Search for a neighbor cache entry
 *
 * @param ip6addr the IPv6 address of the neighbor
 * @param netif the netif on which to send the message
 * @return The neighbor cache entry index that matched, -1 if no
 * entry is found
 */
static s8_t
nd6_find_neighbor_cache_entry(const ip6_addr_t *ip6addr, struct netif *netif)
{
  s8_t i;
  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
    if ((neighbor_cache[i].netif == netif) &&
        (ip6_addr_cmp(ip6addr, &(neighbor_cache[i].next_hop_address)))) {
      return i;
    }
  }
  return -1;
}

/*
 * Create a new neighbor cache entry.
 *
 * If no unused entry is found, will try to recycle an old entry
 * according to ad-hoc "age" heuristic.
 *
 * @return The neighbor cache entry index that was created, -1 if no
 * entry could be created
 */
static s8_t
nd6_new_neighbor_cache_entry(void)
{
  s8_t i;
  s8_t j;
  u32_t curr_time;


  /* First, try to find an empty entry. */
  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
    if (neighbor_cache[i].state == ND6_NO_ENTRY) {
      return i;
    }
  }

  /* We need to recycle an entry. in general, do not recycle if it is a router. */

  /* Next, try to find a Stale entry. */
  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
    if ((neighbor_cache[i].state == ND6_STALE) &&
        (!neighbor_cache[i].isrouter)) {
      nd6_free_neighbor_cache_entry(i);
      return i;
    }
  }

  /* Next, try to find a Probe entry. */
  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
    if ((neighbor_cache[i].state == ND6_PROBE) &&
        (!neighbor_cache[i].isrouter)) {
      nd6_free_neighbor_cache_entry(i);
      return i;
    }
  }

  /* Next, try to find a Delayed entry. */
  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
    if ((neighbor_cache[i].state == ND6_DELAY) &&
        (!neighbor_cache[i].isrouter)) {
      nd6_free_neighbor_cache_entry(i);
      return i;
    }
  }

  /* Next, try to find the oldest reachable entry. */
  curr_time = 0xfffffffful;
  j = -1;
  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
    if ((neighbor_cache[i].state == ND6_REACHABLE) &&
        (!neighbor_cache[i].isrouter)) {
      if (neighbor_cache[i].counter.reachable_time < curr_time) {
        j = i;
        curr_time = neighbor_cache[i].counter.reachable_time;
      }
    }
  }
  if (j >= 0) {
    nd6_free_neighbor_cache_entry(j);
    return j;
  }

  /* Next, find oldest incomplete entry without queued packets. */
  curr_time = 0;
  j = -1;
  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
    if (
        (neighbor_cache[i].q == NULL) &&
        (neighbor_cache[i].state == ND6_INCOMPLETE) &&
        (!neighbor_cache[i].isrouter)) {
      if (neighbor_cache[i].counter.probes_sent >= curr_time) {
        j = i;
        curr_time = neighbor_cache[i].counter.probes_sent;
      }
    }
  }
  if (j >= 0) {
    nd6_free_neighbor_cache_entry(j);
    return j;
  }

  /* Next, find oldest incomplete entry with queued packets. */
  curr_time = 0;
  j = -1;
  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
    if ((neighbor_cache[i].state == ND6_INCOMPLETE) &&
        (!neighbor_cache[i].isrouter)) {
      if (neighbor_cache[i].counter.probes_sent >= curr_time) {
        j = i;
        curr_time = neighbor_cache[i].counter.probes_sent;
      }
    }
  }
  if (j >= 0) {
    nd6_free_neighbor_cache_entry(j);
    return j;
  }

  /* No more entries to try. */
  return -1;
}

/*
 * Will free any resources associated with a neighbor cache
 * entry, and will mark it as unused.
 *
 * @param i the neighbor cache entry index to free
 */
static void
nd6_free_neighbor_cache_entry(s8_t i)
{
  if ((i < 0) || (i >= LWIP_ND6_NUM_NEIGHBORS)) {
    return;
  }
  if (neighbor_cache[i].isrouter) {
    /* isrouter needs to be cleared before deleting a neighbor cache entry */
    return;
  }

  /* Free any queued packets. */
  if (neighbor_cache[i].q != NULL) {
    nd6_free_q(neighbor_cache[i].q);
    neighbor_cache[i].q = NULL;
  }

  neighbor_cache[i].state = ND6_NO_ENTRY;
  neighbor_cache[i].isrouter = 0;
  neighbor_cache[i].netif = NULL;
  neighbor_cache[i].counter.reachable_time = 0;
  ip6_addr_set_zero(&(neighbor_cache[i].next_hop_address));
}

/*
 * Search for a destination cache entry
 *
 * @param ip6addr the IPv6 address of the destination
 * @return The destination cache entry index that matched, -1 if no
 * entry is found
 */
static s8_t
nd6_find_destination_cache_entry(const ip6_addr_t *ip6addr)
{
  s8_t i;
  for (i = 0; i < LWIP_ND6_NUM_DESTINATIONS; i++) {
    if (ip6_addr_cmp(ip6addr, &(destination_cache[i].destination_addr))) {
      return i;
    }
  }
  return -1;
}

/*
 * Create a new destination cache entry. If no unused entry is found,
 * will recycle oldest entry.
 *
 * @return The destination cache entry index that was created, -1 if no
 * entry was created
 */
static s8_t
nd6_new_destination_cache_entry(void)
{
  s8_t i, j;
  u32_t age;

  /* Find an empty entry. */
  for (i = 0; i < LWIP_ND6_NUM_DESTINATIONS; i++) {
    if (ip6_addr_isany(&(destination_cache[i].destination_addr))) {
      return i;
    }
  }

  /* Find oldest entry. */
  age = 0;
  j = LWIP_ND6_NUM_DESTINATIONS - 1;
  for (i = 0; i < LWIP_ND6_NUM_DESTINATIONS; i++) {
    if (destination_cache[i].age > age) {
      j = i;
    }
  }

  return j;
}

/*
 * Clear the destination cache.
 *
 * This operation may be necessary for consistency in the light of changing
 * local addresses and/or use of the gateway hook.
 */
void
nd6_clear_destination_cache(void)
{
  int i;

  for (i = 0; i < LWIP_ND6_NUM_DESTINATIONS; i++) {
    ip6_addr_set_any(&destination_cache[i].destination_addr);
  }
}

/*
 * Determine whether an address matches an on-link prefix.
 *
 * @param ip6addr the IPv6 address to match
 * @return 1 if the address is on-link, 0 otherwise
 */
static s8_t
nd6_is_prefix_in_netif(const ip6_addr_t *ip6addr, struct netif *netif)
{
  s8_t i;

  for (i = 0; i < LWIP_ND6_MAX_PREFIX_ON_INTERFACE; i++) {
    if ((netif->prefix_list[i].invalidation_timer > 0) &&
        ip6_addr_netcmp(ip6addr, &(netif->prefix_list[i].prefix))) {
      return 1;
    }
  }
  /* Check to see if address prefix matches a (manually?) configured address. */
  for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
    if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
        ip6_addr_netcmp(ip6addr, netif_ip6_addr(netif, i))) {
      return 1;
    }
  }
  return 0;
}

/*
 * get  an adressing matching prefix on netif
 *
 * @param ip6addr the IPv6 address to match
 * @return 1 if the address is on-link, 0 otherwise
 */
const ip6_addr_t *
nd6_get_prefix_addr(const ip6_addr_t *ip6addr, struct netif *netif)
{
  s8_t i;
  /* Check to see if address prefix matches a (manually?) configured address. */
  for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
    if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i)) &&
        ip6_addr_netcmp(ip6addr, netif_ip6_addr(netif, i))) {
      return netif_ip6_addr(netif, i);
    }
  }
  return NULL;
}

/*
 * Select a default router for a destination.
 *
 * This function is used both for routing and for finding a next-hop target for
 * a packet. In the former case, the given netif is NULL, and the returned
 * router entry must be for a netif suitable for sending packets (up, link up).
 * In the latter case, the given netif is not NULL and restricts router choice.
 *
 * @param ip6addr the destination address
 * @param netif the netif for the outgoing packet, if known
 * @return the default router entry index, or -1 if no suitable
 *         router is found
 */
static s8_t
nd6_select_router(const ip6_addr_t *ip6addr, struct netif *netif)
{
  struct netif *router_netif;
  s8_t i, j, valid_router;
  static s8_t last_router;

  LWIP_UNUSED_ARG(ip6addr); /* @todo match preferred routes!! (must implement ND6_OPTION_TYPE_ROUTE_INFO) */

  /* @todo: implement default router preference */

  /* Look for valid routers. A reachable router is preferred. */
  valid_router = -1;
  for (i = 0; i < LWIP_ND6_NUM_ROUTERS; i++) {
    /* Is the router netif both set and apppropriate? */
    if (default_router_list[i].neighbor_entry != NULL) {
      router_netif = default_router_list[i].neighbor_entry->netif;
      if ((router_netif != NULL) && (netif != NULL ? netif == router_netif :
          (netif_is_up(router_netif) && netif_is_link_up(router_netif)))) {
        /* Is the router valid, i.e., reachable or probably reachable as per
         * RFC 4861 Sec. 6.3.6? Note that we will never return a router that
         * has no neighbor cache entry, due to the netif association tests. */
        if (default_router_list[i].neighbor_entry->state != ND6_INCOMPLETE) {
          /* Is the router known to be reachable? */
          if (default_router_list[i].neighbor_entry->state == ND6_REACHABLE) {
            return i; /* valid and reachable - done! */
          } else if (valid_router < 0) {
            valid_router = i; /* valid but not known to be reachable */
          }
        }
      }
    }
  }
  if (valid_router >= 0) {
    return valid_router;
  }

  /* Look for any router for which we have any information at all. */
  /* last_router is used for round-robin selection of incomplete routers, as
   * recommended in RFC 4861 Sec. 6.3.6 point (2). Advance only when picking a
   * route, to select the same router as next-hop target in the common case. */
  if ((netif == NULL) && (++last_router >= LWIP_ND6_NUM_ROUTERS)) {
    last_router = 0;
  }
  i = last_router;
  for (j = 0; j < LWIP_ND6_NUM_ROUTERS; j++) {
    if (default_router_list[i].neighbor_entry != NULL) {
      router_netif = default_router_list[i].neighbor_entry->netif;
      if ((router_netif != NULL) && (netif != NULL ? netif == router_netif :
          (netif_is_up(router_netif) && netif_is_link_up(router_netif)))) {
        return i;
      }
    }
    if (++i >= LWIP_ND6_NUM_ROUTERS) {
      i = 0;
    }
  }
  /* no suitable router found. */
  return -1;
}

/**
 @page RFC-8028 RFC-8028
 @par Non-Compliant Section
  Section 3.2. Default Router Selection
 @par Behavior Description
      A host SHOULD select default routers for each prefix it is assigned an address in.
      Routers that have advertised the prefix in their Router Advertisement message
      SHOULD be preferred over routers that do not advertise the prefix, regardless of
      Default Router Preference.
*/
/*
 * Find a router-announced route to the given destination.
 *
 * The caller is responsible for checking whether the returned netif, if any,
 * is in a suitable state (up, link up) to be used for packet transmission.
 *
 * @param ip6addr the destination IPv6 address
 * @return the netif to use for the destination, or NULL if none found
 */
struct netif *
nd6_find_route(const ip6_addr_t *ip6addr)
{
  s8_t i;
  u8_t j;
  struct netif *tmp = NULL;

  /* Check if there is a matching on-link prefix. There may be multiple
   * matches. Pick the first one that is associated with a suitable netif. */
  for (tmp = netif_list; tmp != NULL; tmp = tmp->next) {
    if (!netif_is_up(tmp) || !netif_is_link_up(tmp)) {
      continue;
    }

    for (j = 0; j < LWIP_ND6_MAX_PREFIX_ON_INTERFACE; j++) {
      if (ip6_addr_netcmp(&tmp->prefix_list[j].prefix, ip6addr) &&
          tmp->prefix_list[j].invalidation_timer > 0) {
        return tmp;
      }
    }
  }

  i = nd6_select_router(ip6addr, NULL);
  if (i >= 0) {
    if (default_router_list[i].neighbor_entry != NULL) {
      return default_router_list[i].neighbor_entry->netif; /* may be NULL */
    }
  }

  return NULL;
}

/*
 * Find an entry for a default router.
 *
 * @param router_addr the IPv6 address of the router
 * @param netif the netif on which the router is found, if known
 * @return the index of the router entry, or -1 if not found
 */
static s8_t
nd6_get_router(const ip6_addr_t *router_addr, struct netif *netif)
{
  s8_t i;

  /* Look for router. */
  for (i = 0; i < LWIP_ND6_NUM_ROUTERS; i++) {
    if ((default_router_list[i].neighbor_entry != NULL) &&
        ((netif != NULL) ? netif == default_router_list[i].neighbor_entry->netif : 1) &&
        ip6_addr_cmp(router_addr, &(default_router_list[i].neighbor_entry->next_hop_address))) {
      return i;
    }
  }

  /* router not found. */
  return -1;
}

/*
 * Create a new entry for a default router.
 *
 * @param router_addr the IPv6 address of the router
 * @param netif the netif on which the router is connected, if known
 * @return the index on the router table, or -1 if could not be created
 */
static s8_t
nd6_new_router(const ip6_addr_t *router_addr, struct netif *netif)
{
  s8_t router_index;
  s8_t free_router_index;
  s8_t neighbor_index;

  /* Do we have a neighbor entry for this router? */
  neighbor_index = nd6_find_neighbor_cache_entry(router_addr, netif);
  if (neighbor_index < 0) {
    /* Create a neighbor entry for this router. */
    neighbor_index = nd6_new_neighbor_cache_entry();
    if (neighbor_index < 0) {
      /* Could not create neighbor entry for this router. */
      ND6_STATS_INC(nd6.cacheFull);
      return -1;
    }
    ip6_addr_set(&(neighbor_cache[neighbor_index].next_hop_address), router_addr);
    neighbor_cache[neighbor_index].netif = netif;
    neighbor_cache[neighbor_index].q = NULL;
    neighbor_cache[neighbor_index].state = ND6_INCOMPLETE;
    neighbor_cache[neighbor_index].counter.probes_sent = 1;
    nd6_send_neighbor_cache_probe(&neighbor_cache[neighbor_index], ND6_SEND_FLAG_MULTICAST_DEST);
  }

  /* Mark neighbor as router. */
  neighbor_cache[neighbor_index].isrouter = 1;

  /* Look for empty entry. */
  free_router_index = LWIP_ND6_NUM_ROUTERS;
  for (router_index = LWIP_ND6_NUM_ROUTERS - 1; router_index >= 0; router_index--) {
    /* check if router already exists (this is a special case for 2 netifs on the same subnet
       - e.g. wifi and cable) */
    if(default_router_list[router_index].neighbor_entry == &(neighbor_cache[neighbor_index])){
      return router_index;
    }
    if (default_router_list[router_index].neighbor_entry == NULL) {
      /* remember lowest free index to create a new entry */
      free_router_index = router_index;
    }
  }
  if (free_router_index < LWIP_ND6_NUM_ROUTERS) {
    default_router_list[free_router_index].neighbor_entry = &(neighbor_cache[neighbor_index]);
    return free_router_index;
  }

  /* Could not create a router entry. */

  /* Mark neighbor entry as not-router. Entry might be useful as neighbor still. */
  neighbor_cache[neighbor_index].isrouter = 0;

  /* router not found. */
  return -1;
}

/*
 * Will free any resources associated with a default router list
 * entry, and will mark it as unused.
 *
 * @param i the default entry index to free
 */
static void
nd6_free_router(s8_t i)
{
  if ((i < 0) || (i >= LWIP_ND6_NUM_ROUTERS)) {
    return;
  }

  /*
   * the node MUST remove that router from the Default Router List and update
   * the Destination Cache entries for all destinations using that neighbor as
   * a router as specified in Section 7.3.3.
   */
  if (default_router_list[i].neighbor_entry != NULL){
    s8_t j;
    for (j = 0; j < LWIP_ND6_NUM_DESTINATIONS; j++) {
      if (ip6_addr_cmp(&destination_cache[j].next_hop_addr,
           &default_router_list[i].neighbor_entry->next_hop_address)) {
         ip6_addr_set_any(&destination_cache[j].destination_addr);
      }
    }
    default_router_list[i].neighbor_entry->isrouter = 0;
    default_router_list[i].neighbor_entry = NULL;
    default_router_list[i].invalidation_timer = 0;
    default_router_list[i].flags = 0;
  }
  return;
}

/*
 * Find the cached entry for an on-link prefix.
 *
 * @param prefix the IPv6 prefix that is on-link
 * @param netif the netif on which the prefix is on-link
 * @return the index on the prefix table, or -1 if not found
 */
static s8_t
nd6_get_onlink_prefix(ip6_addr_t *prefix, struct netif *netif)
{
  s8_t i;

  /* Look for prefix in list. */
  for (i = 0; i < LWIP_ND6_MAX_PREFIX_ON_INTERFACE; ++i) {
    if ((netif->prefix_list[i].invalidation_timer > 0)
      && ip6_addr_netcmp(&(netif->prefix_list[i].prefix), prefix)) {
      return i;
    }
  }

  /* Entry not available. */
  return -1;
}

/*
 * Creates a new entry for an on-link prefix.
 *
 * @param prefix the IPv6 prefix that is on-link
 * @param netif the netif on which the prefix is on-link
 * @return the index on the prefix table, or -1 if not created
 */
static s8_t
nd6_new_onlink_prefix(ip6_addr_t *prefix, struct netif *netif)
{
  s8_t i;

  /* Create new entry. */
  for (i = 0; i < LWIP_ND6_MAX_PREFIX_ON_INTERFACE; ++i) {

    if (netif->prefix_list[i].invalidation_timer == 0) {
      /* Found empty prefix entry. */
      ip6_addr_set(&(netif->prefix_list[i].prefix), prefix);
#if LWIP_IPV6_AUTOCONFIG
      netif->prefix_list[i].flags = 0;
#endif /* LWIP_IPV6_AUTOCONFIG */
      return i;
    }
  }

  /* Entry not available. */
  return -1;
}

/*
 * Determine the next hop for a destination. Will determine if the
 * destination is on-link, else a suitable on-link router is selected.
 *
 * The last entry index is cached for fast entry search.
 *
 * @param ip6addr the destination address
 * @param netif the netif on which the packet will be sent
 * @return the neighbor cache entry for the next hop, ERR_RTE if no
 *         suitable next hop was found, ERR_MEM if no cache entry
 *         could be created
 */
static s8_t
nd6_get_next_hop_entry(const ip6_addr_t *ip6addr, struct netif *netif)
{
#ifdef LWIP_HOOK_ND6_GET_GW
  const ip6_addr_t *next_hop_addr;
#endif /* LWIP_HOOK_ND6_GET_GW */
  s8_t i;

#if LWIP_NETIF_HWADDRHINT
  if (netif->addr_hint != NULL) {
    /* per-pcb cached entry was given */
    u8_t addr_hint = *(netif->addr_hint);
    if (addr_hint < LWIP_ND6_NUM_DESTINATIONS) {
      nd6_cached_destination_index = addr_hint;
    }
  }
#endif /* LWIP_NETIF_HWADDRHINT */

  /* Look for ip6addr in destination cache. */
  if (ip6_addr_cmp(ip6addr, &(destination_cache[nd6_cached_destination_index].destination_addr))) {
    /* the cached entry index is the right one! */
    /* do nothing. */
    ND6_STATS_INC(nd6.cachehit);
  } else {
    /* Search destination cache. */
    i = nd6_find_destination_cache_entry(ip6addr);
    if (i >= 0) {
      /* found destination entry. make it our new cached index. */
      nd6_cached_destination_index = (u8_t)i;
    } else {
      /* Not found. Create a new destination entry. */
      i = nd6_new_destination_cache_entry();
      if (i >= 0) {
        /* got new destination entry. make it our new cached index. */
        nd6_cached_destination_index = (u8_t)i;
      } else {
        /* Could not create a destination cache entry. */
        ND6_STATS_INC(nd6.cacheFull);
        return ERR_MEM;
      }

      /* Copy dest address to destination cache. */
      ip6_addr_set_val(&(destination_cache[nd6_cached_destination_index].destination_addr), ip6addr);

      /* Now find the next hop. is it a neighbor? */
      if (ip6_addr_islinklocal(ip6addr) ||
          nd6_is_prefix_in_netif(ip6addr, netif)) {
        /* Destination in local link. */
        destination_cache[nd6_cached_destination_index].pmtu = netif_mtu6(netif);;
        ip6_addr_copy(destination_cache[nd6_cached_destination_index].next_hop_addr, destination_cache[nd6_cached_destination_index].destination_addr);
#ifdef LWIP_HOOK_ND6_GET_GW
      } else if ((next_hop_addr = LWIP_HOOK_ND6_GET_GW(netif, ip6addr)) != NULL) {
        /* Next hop for destination provided by hook function. */
        destination_cache[nd6_cached_destination_index].pmtu = netif->mtu;
        ip6_addr_set(&destination_cache[nd6_cached_destination_index].next_hop_addr, next_hop_addr);
#endif /* LWIP_HOOK_ND6_GET_GW */
      } else {
        /* We need to select a router. */
        i = nd6_select_router(ip6addr, netif);
        if (i < 0) {
          /* No router found. */
          ip6_addr_set_any(&(destination_cache[nd6_cached_destination_index].destination_addr));
          return ERR_RTE;
        }
        destination_cache[nd6_cached_destination_index].pmtu = netif_mtu6(netif);; /* Start with netif mtu, correct through ICMPv6 if necessary */
        ip6_addr_copy(destination_cache[nd6_cached_destination_index].next_hop_addr, default_router_list[i].neighbor_entry->next_hop_address);
      }
    }
  }

#if LWIP_NETIF_HWADDRHINT
  if (netif->addr_hint != NULL) {
    /* per-pcb cached entry was given */
    *(netif->addr_hint) = nd6_cached_destination_index;
  }
#endif /* LWIP_NETIF_HWADDRHINT */

  /* Look in neighbor cache for the next-hop address. */
  if (ip6_addr_cmp(&(destination_cache[nd6_cached_destination_index].next_hop_addr),
                   &(neighbor_cache[nd6_cached_neighbor_index].next_hop_address))) {
    /* Cache hit. */
    /* Do nothing. */
    ND6_STATS_INC(nd6.cachehit);
  } else {
    i = nd6_find_neighbor_cache_entry(&(destination_cache[nd6_cached_destination_index].next_hop_addr), netif);
    if (i >= 0) {
      /* Found a matching record, make it new cached entry. */
      nd6_cached_neighbor_index = (u8_t)i;
    } else {
      /* Neighbor not in cache. Make a new entry. */
      i = nd6_new_neighbor_cache_entry();
      if (i >= 0) {
        /* got new neighbor entry. make it our new cached index. */
        nd6_cached_neighbor_index = (u8_t)i;
      } else {
        /* Could not create a neighbor cache entry. */
        ND6_STATS_INC(nd6.cacheFull);
        return ERR_MEM;
      }

      /* Initialize fields. */
      ip6_addr_copy(neighbor_cache[i].next_hop_address,
                   destination_cache[nd6_cached_destination_index].next_hop_addr);
      neighbor_cache[i].isrouter = 0;
      neighbor_cache[i].netif = netif;
      neighbor_cache[i].state = ND6_INCOMPLETE;
      neighbor_cache[i].counter.probes_sent = 1;
      nd6_send_neighbor_cache_probe(&neighbor_cache[i], ND6_SEND_FLAG_MULTICAST_DEST);
    }
  }

  /* Reset this destination's age. */
  destination_cache[nd6_cached_destination_index].age = 0;

  return (s8_t)nd6_cached_neighbor_index;
}


s8_t  netif_find_dst_ip6addr_mac_addr(ip_addr_t *ipaddr, ip_addr_t **dst_addr,struct eth_addr **eth_ret)
{
  struct netif *netif;

  int dst_index = 0;
  int neighbour_index = 0;

  netif = netif_find_by_ipaddr(ipaddr);
  if(netif == NULL) {
      return -1;
  }

  dst_index = nd6_find_destination_cache_entry(&((*dst_addr)->u_addr.ip6));
  if(dst_index >= 0)
  {
    neighbour_index = nd6_find_neighbor_cache_entry(&(destination_cache[dst_index].next_hop_addr), netif);
    if(neighbour_index >= 0)
    {
      (void)memcpy_s((*eth_ret)->addr,sizeof(struct eth_addr),neighbor_cache[neighbour_index].lladdr,NETIF_MAX_HWADDR_LEN);
    }
    else
    {
      return -1;
    }
  }
  else
  {
      return -1;
  }
  return 0;
}

/*
 * Queue a packet for a neighbor.
 *
 * @param neighbor_index the index in the neighbor cache table
 * @param q packet to be queued
 * @return ERR_OK if succeeded, ERR_MEM if out of memory
 */
static err_t
nd6_queue_packet(s8_t neighbor_index, struct pbuf *q)
{
  err_t result = ERR_MEM;
  struct pbuf *p;
  int copy_needed = 0;
#if LWIP_ND6_QUEUEING
  struct nd6_q_entry *new_entry, *r;
#endif /* LWIP_ND6_QUEUEING */

  if ((neighbor_index < 0) || (neighbor_index >= LWIP_ND6_NUM_NEIGHBORS)) {
    return ERR_ARG;
  }

  /* IF q includes a PBUF_REF, PBUF_POOL or PBUF_RAM, we have no choice but
   * to copy the whole queue into a new PBUF_RAM (see bug #11400)
   * PBUF_ROMs can be left as they are, since ROM must not get changed. */
  p = q;
  while (p != NULL) {
    if (p->type != PBUF_ROM) {
      copy_needed = 1;
      break;
    }
    p = p->next;
  }
  if (copy_needed) {
    /* copy the whole packet into new pbufs */
    p = pbuf_alloc(PBUF_LINK, q->tot_len, PBUF_RAM);
    while ((p == NULL) && (neighbor_cache[neighbor_index].q != NULL)) {
      /* Free oldest packet (as per RFC recommendation) */
#if LWIP_ND6_QUEUEING
      r = neighbor_cache[neighbor_index].q;
      neighbor_cache[neighbor_index].q = r->next;
      r->next = NULL;
      nd6_free_q(r);
#else /* LWIP_ND6_QUEUEING */
      (void)pbuf_free(neighbor_cache[neighbor_index].q);
      neighbor_cache[neighbor_index].q = NULL;
#endif /* LWIP_ND6_QUEUEING */
      p = pbuf_alloc(PBUF_LINK, q->tot_len, PBUF_RAM);
    }
    if (p != NULL) {
      if (pbuf_copy(p, q) != ERR_OK) {
        (void)pbuf_free(p);
        p = NULL;
      }
    }
  } else {
    /* referencing the old pbuf is enough */
    p = q;
    pbuf_ref(p);
  }
  /* packet was copied/ref'd? */
  if (p != NULL) {
    /* queue packet ... */
#if LWIP_ND6_QUEUEING
    /* allocate a new nd6 queue entry */
    new_entry = (struct nd6_q_entry *)memp_malloc(MEMP_ND6_QUEUE);
    if ((new_entry == NULL) && (neighbor_cache[neighbor_index].q != NULL)) {
      /* Free oldest packet (as per RFC recommendation) */
      r = neighbor_cache[neighbor_index].q;
      neighbor_cache[neighbor_index].q = r->next;
      r->next = NULL;
      nd6_free_q(r);
      new_entry = (struct nd6_q_entry *)memp_malloc(MEMP_ND6_QUEUE);
    }
    if (new_entry != NULL) {
      new_entry->next = NULL;
      new_entry->p = p;
      if (neighbor_cache[neighbor_index].q != NULL) {
        /* queue was already existent, append the new entry to the end */
        r = neighbor_cache[neighbor_index].q;
        while (r->next != NULL) {
          r = r->next;
        }
        r->next = new_entry;
      } else {
        /* queue did not exist, first item in queue */
        neighbor_cache[neighbor_index].q = new_entry;
      }
      LWIP_DEBUGF(LWIP_DBG_TRACE, ("ipv6: queued packet %p on neighbor entry %"S16_F"\n", (void *)p, (s16_t)neighbor_index));
      result = ERR_OK;
    } else {
      /* the pool MEMP_ND6_QUEUE is empty */
      (void)pbuf_free(p);
      LWIP_DEBUGF(LWIP_DBG_TRACE, ("ipv6: could not queue a copy of packet %p (out of memory)\n", (void *)p));
      /* { result == ERR_MEM } through initialization */
    }
#else /* LWIP_ND6_QUEUEING */
    /* Queue a single packet. If an older packet is already queued, free it as per RFC. */
    if (neighbor_cache[neighbor_index].q != NULL) {
      (void)pbuf_free(neighbor_cache[neighbor_index].q);
    }
    neighbor_cache[neighbor_index].q = p;
    LWIP_DEBUGF(LWIP_DBG_TRACE, ("ipv6: queued packet %p on neighbor entry %"S16_F"\n", (void *)p, (s16_t)neighbor_index));
    result = ERR_OK;
#endif /* LWIP_ND6_QUEUEING */
  } else {
    LWIP_DEBUGF(LWIP_DBG_TRACE, ("ipv6: could not queue a copy of packet %p (out of memory)\n", (void *)q));
    /* { result == ERR_MEM } through initialization */
  }

  return result;
}

#if LWIP_ND6_QUEUEING
/*
 * Free a complete queue of nd6 q entries
 *
 * @param q a queue of nd6_q_entry to free
 */
static void
nd6_free_q(struct nd6_q_entry *q)
{
  struct nd6_q_entry *r;
  LWIP_ASSERT("q != NULL", q != NULL);
  LWIP_ASSERT("q->p != NULL", q->p != NULL);
  while (q) {
    r = q;
    q = q->next;
    LWIP_ASSERT("r->p != NULL", (r->p != NULL));
    (void)pbuf_free(r->p);
    memp_free(MEMP_ND6_QUEUE, r);
  }
}
#endif /* LWIP_ND6_QUEUEING */

/*
 * Send queued packets for a neighbor
 *
 * @param i the neighbor to send packets to
 */
static void
nd6_send_q(s8_t i)
{
  struct ip6_hdr *ip6hdr;
  ip6_addr_t dest;
#if LWIP_ND6_QUEUEING
  struct nd6_q_entry *q;
#endif /* LWIP_ND6_QUEUEING */

  if ((i < 0) || (i >= LWIP_ND6_NUM_NEIGHBORS)) {
    return;
  }

#if LWIP_ND6_QUEUEING
  while (neighbor_cache[i].q != NULL) {
    /* remember first in queue */
    q = neighbor_cache[i].q;
    /* pop first item off the queue */
    neighbor_cache[i].q = q->next;
    /* Get ipv6 header. */
    ip6hdr = (struct ip6_hdr *)(q->p->payload);
    /* Create an aligned copy. */
    ip6_addr_set(&dest, &(ip6hdr->dest));

    LWIP_DEBUGF(LWIP_DBG_TRACE, ("ipv6: queued packet %p on neighbor entry %"S16_F"\n", (void *)q->p, (s16_t)i));

    /* send the queued IPv6 packet */
    (void)(neighbor_cache[i].netif)->output_ip6(neighbor_cache[i].netif, q->p, &dest);
    /* free the queued IP packet */
    (void)pbuf_free(q->p);
    /* now queue entry can be freed */
    memp_free(MEMP_ND6_QUEUE, q);
  }
#else /* LWIP_ND6_QUEUEING */
  if (neighbor_cache[i].q != NULL) {
    /* Get ipv6 header. */
    ip6hdr = (struct ip6_hdr *)(neighbor_cache[i].q->payload);
    /* Create an aligned copy. */
    ip6_addr_set(&dest, &(ip6hdr->dest));
    /* send the queued IPv6 packet */
    (neighbor_cache[i].netif)->output_ip6(neighbor_cache[i].netif, neighbor_cache[i].q, &dest);
    /* free the queued IP packet */
    (void)pbuf_free(neighbor_cache[i].q);
    neighbor_cache[i].q = NULL;
  }
#endif /* LWIP_ND6_QUEUEING */
}

/*
 * A packet is to be transmitted to a specific IPv6 destination on a specific
 * interface. Check if we can find the hardware address of the next hop to use
 * for the packet. If so, give the hardware address to the caller, which should
 * use it to send the packet right away. Otherwise, enqueue the packet for
 * later transmission while looking up the hardware address, if possible.
 *
 * As such, this function returns one of three different possible results:
 *
 * - ERR_OK with a non-NULL 'hwaddrp': the caller should send the packet now.
 * - ERR_OK with a NULL 'hwaddrp': the packet has been enqueued for later.
 * - not ERR_OK: something went wrong; forward the error upward in the stack.
 *
 * @param netif The lwIP network interface on which the IP packet will be sent.
 * @param q The pbuf(s) containing the IP packet to be sent.
 * @param ip6addr The destination IPv6 address of the packet.
 * @param hwaddrp On success, filled with a pointer to a HW address or NULL (meaning
 *        the packet has been queued).
 * @return
 * - ERR_OK on success, ERR_RTE if no route was found for the packet,
 * or ERR_MEM if low memory conditions prohibit sending the packet at all.
 */
err_t
nd6_get_next_hop_addr_or_queue(struct netif *netif, struct pbuf *q, const ip6_addr_t *ip6addr, const u8_t **hwaddrp)
{
  s8_t i;

  /* Get next hop record. */
  i = nd6_get_next_hop_entry(ip6addr, netif);
  if (i < 0) {
    /* failed to get a next hop neighbor record. */
    return i;
  }

  /* Now that we have a destination record, send or queue the packet. */
  if (neighbor_cache[i].state == ND6_STALE) {
    /* Switch to delay state. */
    neighbor_cache[i].state = ND6_DELAY;
    neighbor_cache[i].counter.delay_time = LWIP_ND6_DELAY_FIRST_PROBE_TIME / ND6_TMR_INTERVAL;
  }
  /* @todo should we send or queue if PROBE? send for now, to let unicast NS pass. */
  if ((neighbor_cache[i].state == ND6_REACHABLE) ||
      (neighbor_cache[i].state == ND6_DELAY) ||
      (neighbor_cache[i].state == ND6_PROBE)) {

    /* Tell the caller to send out the packet now. */
    *hwaddrp = neighbor_cache[i].lladdr;
    return ERR_OK;
  }

  /* We should queue packet on this interface. */
  *hwaddrp = NULL;
  return nd6_queue_packet(i, q);
}


/*
 * Get the Path MTU for a destination.
 *
 * @param ip6addr the destination address
 * @param netif the netif on which the packet will be sent
 * @return the Path MTU, if known, or the netif default MTU
 */
u16_t
nd6_get_destination_mtu(const ip6_addr_t *ip6addr, struct netif *netif)
{
  s8_t i;

  i = nd6_find_destination_cache_entry(ip6addr);
  if (i >= 0) {
    if (destination_cache[i].pmtu > 0) {
      return destination_cache[i].pmtu;
    }
  }

  if (netif != NULL) {
    return netif_mtu6(netif);
  }
  return NETIF_MTU_MIN; /* Minimum MTU */
}


#if LWIP_ND6_TCP_REACHABILITY_HINTS
/*
 * Provide the Neighbor discovery process with a hint that a
 * destination is reachable. Called by tcp_receive when ACKs are
 * received or sent (as per RFC). This is useful to avoid sending
 * NS messages every 30 seconds.
 *
 * @param ip6addr the destination address which is know to be reachable
 *                by an upper layer protocol (TCP)
 */
void
nd6_reachability_hint(const ip6_addr_t *ip6addr)
{
  s8_t i;
  struct netif *netif;

  /* Find destination in cache. */
  if (ip6_addr_cmp(ip6addr, &(destination_cache[nd6_cached_destination_index].destination_addr))) {
    i = (s8_t)nd6_cached_destination_index;
    ND6_STATS_INC(nd6.cachehit);
  } else {
    i = nd6_find_destination_cache_entry(ip6addr);
  }
  if (i < 0) {
    return;
  }

  /* Find next hop neighbor in cache. */
  if (ip6_addr_cmp(&(destination_cache[i].next_hop_addr), &(neighbor_cache[nd6_cached_neighbor_index].next_hop_address))) {
    i = (s8_t)nd6_cached_neighbor_index;
    ND6_STATS_INC(nd6.cachehit);
  } else {
    netif = netif_find_by_ipaddr(ip_current_dest_addr());
    i = nd6_find_neighbor_cache_entry(&(destination_cache[i].next_hop_addr), netif);
  }

  if (i < 0) {
    ND6_STATS_INC(nd6.cacheMiss);
    return;
  }

  /* For safety: don't set as reachable if we don't have a LL address yet. Misuse protection. */
  if (neighbor_cache[i].state == ND6_INCOMPLETE || neighbor_cache[i].state == ND6_NO_ENTRY) {
    return;
  }

  /* Set reachability state. */
  neighbor_cache[i].state = ND6_REACHABLE;
  neighbor_cache[i].counter.reachable_time = reachable_time;
}
#endif /* LWIP_ND6_TCP_REACHABILITY_HINTS */

/*
 * Remove all prefix, neighbor_cache and router entries of the specified netif.
 *
 * @param netif points to a network interface
 */
void
nd6_cleanup_netif(struct netif *netif)
{
  u8_t i,j;
  s8_t router_index;

  for (i = 0; i < LWIP_ND6_MAX_PREFIX_ON_INTERFACE; i++) {
      netif->prefix_list[i].invalidation_timer = 0;
      netif->prefix_list[i].flags = 0;
  }

  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++)
  {
    if (neighbor_cache[i].netif == netif)
    {
      for (j = 0; j < LWIP_ND6_NUM_DESTINATIONS; j++)
      {
        if (ip6_addr_cmp(&(destination_cache[j].next_hop_addr), &(neighbor_cache[i].next_hop_address)))
        {
          ip6_addr_set_any(&destination_cache[j].destination_addr);
          ip6_addr_set_any(&destination_cache[j].next_hop_addr);
          destination_cache[j].pmtu = IP_FRAG_MAX_MTU;
          destination_cache[j].age =0;
        }
      }
    }
  }

  for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
    if (neighbor_cache[i].netif == netif) {
      for (router_index = 0; router_index < LWIP_ND6_NUM_ROUTERS; router_index++) {
        if (default_router_list[router_index].neighbor_entry == &neighbor_cache[i]) {
          default_router_list[router_index].neighbor_entry = NULL;
          default_router_list[router_index].flags = 0;
        }
      }
      neighbor_cache[i].isrouter = 0;
      nd6_free_neighbor_cache_entry((s8_t)i);
    }
  }
  /* Clear the destination cache, since many entries may now have become
   * invalid for one of several reasons. As destination cache entries have no
   * netif association, use a sledgehammer approach (this can be improved). */
  nd6_clear_destination_cache();
}

#if LWIP_IPV6_MLD
/*
 * The state of a local IPv6 address entry is about to change. If needed, join
 * or leave the solicited-node multicast group for the address.
 *
 * @param netif The netif that owns the address.
 * @param addr_idx The index of the address.
 * @param new_state The new (IP6_ADDR_) state for the address.
 */
void
nd6_adjust_mld_membership(struct netif *netif, s8_t addr_idx, u8_t new_state)
{
  u8_t old_state, old_member, new_member;

  old_state = netif_ip6_addr_state(netif, addr_idx);

  /* Determine whether we were, and should be, a member of the solicited-node
   * multicast group for this address. For tentative addresses, the group is
   * not joined until the address enters the TENTATIVE_1 (or VALID) state. */
  old_member = (old_state != IP6_ADDR_INVALID && old_state != IP6_ADDR_DUPLICATED && old_state != IP6_ADDR_TENTATIVE);
  new_member = (new_state != IP6_ADDR_INVALID && new_state != IP6_ADDR_DUPLICATED && new_state != IP6_ADDR_TENTATIVE);

  if (old_member != new_member) {
    ip6_addr_set_solicitednode(&multicast_address, netif_ip6_addr(netif, addr_idx)->addr[3]);

    if (new_member) {
      (void)mld6_joingroup_netif(netif, &multicast_address);
    } else {
      (void)mld6_leavegroup_netif(netif, &multicast_address);
    }
  }
}
#endif /* LWIP_IPV6_MLD */

#if LWIP_IPV6_AUTOCONFIG
/*
 * clear the netif's IPV6 address which is autoconfiged from RA
 *
 * @param netif the netif to clear IPV6 address
 */
void
nd6_clear_netif_autoconf_address(struct netif *netif)
{
  s8_t i, j;

  if(NULL == netif)
    return;

  /* Skip 0 index (link-local address) */
  for (j = 1; j < LWIP_IPV6_NUM_ADDRESSES; j++) {
    if (netif_ip6_addr_state(netif, j) != IP6_ADDR_INVALID) {
      i = nd6_get_onlink_prefix((ip6_addr_t *)netif_ip6_addr(netif, j), netif);
      if (i >= 0) {
        netif_ip6_addr_set_state(netif, j, IP6_ADDR_INVALID);
        netif->prefix_list[i].flags = (u8_t)(netif->prefix_list[i].flags & (u8_t)(~ND6_PREFIX_AUTOCONFIG_ADDRESS_GENERATED));
      }
    }
  }

  return;
}
#endif

/* Netif was added, set up, or reconnected (link up) */
void
nd6_restart_netif(struct netif *netif)
{
#if LWIP_IPV6_SEND_ROUTER_SOLICIT
  /* Send Router Solicitation messages (see RFC 4861, ch. 6.3.7). */
  netif->rs_count = LWIP_ND6_MAX_MULTICAST_SOLICIT;
#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */
}
/*
 * Report ND6 messages for this interface
 *
 * @param netif network interface on which report ND6 messages
 */
void
nd6_report_groups(struct netif *netif, s8_t addr_idx)
{
  if (netif->flags & (NETIF_FLAG_ETHARP)) {
    int i;
    /* Publish all new address after hw addr changed, note : link local address remains same,
      since it needs to be reconfigured by user.
    */
    /* RFC4861 section 7.2.6: The Override flag MAY be set to either zero or one. In either case, neighboring
      nodes will immediately change the state of their Neighbor Cache entries for the Target Address to STALE,
      prompting them to verify the path for reachability. If the Override flag is set to one, neighboring nodes will
      install the new link-layer address in their caches. Otherwise, they will ignore the new link-layer address,
      choosing instead to probe the cached address. */
    /* lwip sets Override flag to one to make the neighboring nodes update their neighbor cache immediately,
      this would avoid network failure if the peer side still use the old hw address. */
    if ((s8_t)LWIP_INVALID_IPV6_IDX == addr_idx) {
      for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
        if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) {
          nd6_send_na(netif, netif_ip6_addr(netif, i), ND6_FLAG_OVERRIDE | ND6_SEND_FLAG_ALLNODES_DEST);
        }
      }
    } else {
      if (ip6_addr_isvalid(netif_ip6_addr_state(netif, addr_idx))) {
          nd6_send_na(netif, netif_ip6_addr(netif, addr_idx), ND6_FLAG_OVERRIDE | ND6_SEND_FLAG_ALLNODES_DEST);
      }
    }
  }
}
#endif /* LWIP_IPV6 */
